preserves 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.irbrc +9 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +178 -0
- data/Rakefile +2 -0
- data/SETUP.sh +15 -0
- data/TODO.md +122 -0
- data/lib/preserves.rb +24 -0
- data/lib/preserves/mapper.rb +108 -0
- data/lib/preserves/mapper/belongs_to.rb +25 -0
- data/lib/preserves/mapper/has_many.rb +25 -0
- data/lib/preserves/mapper/relation.rb +25 -0
- data/lib/preserves/mapping.rb +73 -0
- data/lib/preserves/repository.rb +69 -0
- data/lib/preserves/selection.rb +65 -0
- data/lib/preserves/sql.rb +12 -0
- data/lib/preserves/sql/result_set.rb +21 -0
- data/lib/preserves/version.rb +3 -0
- data/preserves.gemspec +27 -0
- data/spec/repository_spec.rb +260 -0
- data/spec/spec_helper.rb +120 -0
- metadata +140 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require "preserves/mapper/relation"
|
2
|
+
|
3
|
+
module Preserves
|
4
|
+
class Mapper
|
5
|
+
class BelongsTo < Relation
|
6
|
+
|
7
|
+
def map!
|
8
|
+
assign_attribute(object, relation_name, relation_repo.map_one(relation_result_for_this_object))
|
9
|
+
end
|
10
|
+
|
11
|
+
def relation_result_for_this_object
|
12
|
+
@relation_result_for_this_object ||= relation_result_set.find{ |r| r[relation_repo.mapping.primary_key] == record.fetch(relation_foreign_key) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def relation_foreign_key
|
16
|
+
@relation_foreign_key ||= relation_settings.fetch(:foreign_key) { "#{relation_name.downcase}_id" }
|
17
|
+
end
|
18
|
+
|
19
|
+
def relation_settings
|
20
|
+
@relation_settings ||= mapping.belongs_to_mappings.fetch(relation_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "preserves/mapper/relation"
|
2
|
+
|
3
|
+
module Preserves
|
4
|
+
class Mapper
|
5
|
+
class HasMany < Relation
|
6
|
+
|
7
|
+
def map!
|
8
|
+
assign_attribute(object, relation_name, relation_repo.map(relation_results_for_this_object))
|
9
|
+
end
|
10
|
+
|
11
|
+
def relation_results_for_this_object
|
12
|
+
@relation_results_for_this_object ||= relation_result_set.select{ |r| r[relation_foreign_key] == record.fetch(mapping.primary_key) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def relation_foreign_key
|
16
|
+
@relation_foreign_key ||= relation_settings.fetch(:foreign_key) { "#{mapping.model_class.to_s.downcase}_id" }
|
17
|
+
end
|
18
|
+
|
19
|
+
def relation_settings
|
20
|
+
@relation_settings ||= mapping.has_many_mappings.fetch(relation_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Preserves
|
2
|
+
class Mapper
|
3
|
+
class Relation
|
4
|
+
|
5
|
+
attr_reader :object, :record, :relation_name, :relation_result_set, :mapping
|
6
|
+
|
7
|
+
def initialize(object, record, relation_name, relation_result_set, mapping)
|
8
|
+
@object = object
|
9
|
+
@record = record
|
10
|
+
@relation_name = relation_name
|
11
|
+
@relation_result_set = relation_result_set
|
12
|
+
@mapping = mapping
|
13
|
+
end
|
14
|
+
|
15
|
+
def relation_repo
|
16
|
+
@relation_repo ||= relation_settings.fetch(:repository) # TODO: Need a default.
|
17
|
+
end
|
18
|
+
|
19
|
+
def assign_attribute(object, attribute_name, value)
|
20
|
+
object.send("#{attribute_name}=", value)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Preserves
|
2
|
+
class Mapping
|
3
|
+
|
4
|
+
attr_accessor :repository
|
5
|
+
attr_accessor :model_class
|
6
|
+
attr_accessor :name_mappings
|
7
|
+
attr_accessor :type_mappings
|
8
|
+
attr_accessor :has_many_mappings
|
9
|
+
attr_accessor :belongs_to_mappings
|
10
|
+
|
11
|
+
def initialize(repository, model_class, &block)
|
12
|
+
table_name pluralize(model_class.name.downcase)
|
13
|
+
primary_key "id"
|
14
|
+
self.repository = repository
|
15
|
+
self.model_class = model_class
|
16
|
+
self.name_mappings = {}
|
17
|
+
self.type_mappings = {}
|
18
|
+
self.has_many_mappings = {}
|
19
|
+
self.belongs_to_mappings = {}
|
20
|
+
self.instance_eval(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Note that this works to set or get the table name.
|
24
|
+
# TODO: We don't want to allow publicly setting this, but we need to publicly get it.
|
25
|
+
def table_name(name=nil)
|
26
|
+
@table_name = name unless name.nil?
|
27
|
+
@table_name
|
28
|
+
end
|
29
|
+
|
30
|
+
# Note that this works to set or get the primary key.
|
31
|
+
# TODO: We don't want to allow publicly setting this, but we need to publicly get it.
|
32
|
+
def primary_key(key_name=nil)
|
33
|
+
@primary_key = key_name unless key_name.nil?
|
34
|
+
@primary_key
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def map(*args)
|
40
|
+
if args[0].is_a?(Hash)
|
41
|
+
database_field_name = args[0].values.first
|
42
|
+
model_attribute_name = args[0].keys.first
|
43
|
+
self.name_mappings[database_field_name] = model_attribute_name
|
44
|
+
elsif args[0].is_a?(Symbol)
|
45
|
+
model_attribute_name = args[0]
|
46
|
+
end
|
47
|
+
|
48
|
+
if args[1].is_a?(Class)
|
49
|
+
self.type_mappings[model_attribute_name] = args[1]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def has_many(related_attribute_name, options)
|
54
|
+
self.has_many_mappings[related_attribute_name] = options
|
55
|
+
end
|
56
|
+
|
57
|
+
def belongs_to(related_attribute_name, options)
|
58
|
+
self.belongs_to_mappings[related_attribute_name] = options
|
59
|
+
end
|
60
|
+
|
61
|
+
def pluralize(string)
|
62
|
+
case string
|
63
|
+
when /(x|ch|ss|sh)$/i
|
64
|
+
string + "es"
|
65
|
+
when /s$/i
|
66
|
+
string
|
67
|
+
else
|
68
|
+
string + "s"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "preserves/selection"
|
2
|
+
require "preserves/mapping"
|
3
|
+
require "preserves/mapper"
|
4
|
+
|
5
|
+
|
6
|
+
module Preserves
|
7
|
+
class Repository
|
8
|
+
|
9
|
+
attr_accessor :model_class
|
10
|
+
|
11
|
+
def initialize(options={})
|
12
|
+
self.model_class = options[:model]
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch(primary_key_value)
|
16
|
+
select(fetch_query, primary_key_value).only
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch!(primary_key_value)
|
20
|
+
select(fetch_query, primary_key_value).only!
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :[], :fetch
|
24
|
+
|
25
|
+
def map(result, relations={})
|
26
|
+
mapper.map(result, relations)
|
27
|
+
end
|
28
|
+
|
29
|
+
def map_one(result, relations={})
|
30
|
+
mapper.map_one(result, relations)
|
31
|
+
end
|
32
|
+
|
33
|
+
def mapping(&block)
|
34
|
+
@mapping ||= Mapping.new(self, model_class, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def query(sql_string, *params)
|
40
|
+
pg_result = data_store.exec_params(sql_string, params)
|
41
|
+
SQL::ResultSet.new(pg_result)
|
42
|
+
end
|
43
|
+
|
44
|
+
def select(sql_string, *params)
|
45
|
+
if params && params.last.is_a?(Hash)
|
46
|
+
relations = params.pop
|
47
|
+
else
|
48
|
+
relations = {}
|
49
|
+
end
|
50
|
+
Selection.new(map(query(sql_string, *params), relations))
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def mapper
|
56
|
+
@mapper ||= Mapper.new(@mapping)
|
57
|
+
end
|
58
|
+
|
59
|
+
# NOTE: We'll allow overriding this default on a per-repository basis later.
|
60
|
+
def data_store
|
61
|
+
Preserves.data_store
|
62
|
+
end
|
63
|
+
|
64
|
+
def fetch_query
|
65
|
+
"SELECT * FROM \"#{mapping.table_name}\" WHERE #{mapping.primary_key} = $1"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Preserves
|
2
|
+
class Selection
|
3
|
+
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_accessor :domain_objects
|
7
|
+
|
8
|
+
def initialize(domain_objects)
|
9
|
+
self.domain_objects = domain_objects
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
domain_objects.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def size
|
17
|
+
domain_objects.size
|
18
|
+
end
|
19
|
+
|
20
|
+
def first
|
21
|
+
domain_objects.first
|
22
|
+
end
|
23
|
+
|
24
|
+
def first!
|
25
|
+
fail "expected at least 1 result" if size == 0
|
26
|
+
domain_objects.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def second
|
30
|
+
domain_objects.second
|
31
|
+
end
|
32
|
+
|
33
|
+
def second!
|
34
|
+
fail "expected at least 1 result" if size == 0
|
35
|
+
domain_objects.second
|
36
|
+
end
|
37
|
+
|
38
|
+
def last
|
39
|
+
domain_objects.last
|
40
|
+
end
|
41
|
+
|
42
|
+
def last!
|
43
|
+
fail "expected at least 1 result" if size == 0
|
44
|
+
domain_objects.last
|
45
|
+
end
|
46
|
+
|
47
|
+
def only
|
48
|
+
fail "expected only 1 result" if size > 1
|
49
|
+
domain_objects.first
|
50
|
+
end
|
51
|
+
|
52
|
+
def only!
|
53
|
+
fail "expected exactly 1 result" if size != 1
|
54
|
+
domain_objects.first
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :one, :only
|
58
|
+
alias_method :one!, :only!
|
59
|
+
|
60
|
+
def [](index)
|
61
|
+
domain_objects[index]
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Preserves
|
2
|
+
module SQL
|
3
|
+
class ResultSet
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(pg_result)
|
8
|
+
@pg_result = pg_result
|
9
|
+
end
|
10
|
+
|
11
|
+
def size
|
12
|
+
@pg_result.ntuples == 0 ? @pg_result.cmd_tuples : @pg_result.ntuples
|
13
|
+
end
|
14
|
+
|
15
|
+
def each(&block)
|
16
|
+
@pg_result.each(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/preserves.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'preserves/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "preserves"
|
8
|
+
spec.version = Preserves::VERSION
|
9
|
+
spec.authors = ["Craig Buchek"]
|
10
|
+
spec.email = ["craig@boochtek.com"]
|
11
|
+
spec.summary = %q{Minimalist ORM, using the Data Mapper pattern}
|
12
|
+
spec.description = %q{Experimental, opinionated, minimalist ORM (object-relational mapper) for Ruby, using the Data Mapper pattern.}
|
13
|
+
spec.homepage = "https://github.com/boochtek/ruby_preserves"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "pg", "~> 0.18.3"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.4"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.3"
|
26
|
+
spec.add_development_dependency "rubygems-tasks", "~> 0.2"
|
27
|
+
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require "preserves"
|
3
|
+
|
4
|
+
class Group
|
5
|
+
attr_accessor :name
|
6
|
+
end
|
7
|
+
|
8
|
+
class User
|
9
|
+
attr_accessor :id
|
10
|
+
attr_accessor :age
|
11
|
+
attr_accessor :addresses
|
12
|
+
attr_accessor :group
|
13
|
+
end
|
14
|
+
|
15
|
+
class Address
|
16
|
+
attr_accessor :city
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
AddressRepository = Preserves.repository(model: Address) do
|
21
|
+
mapping do
|
22
|
+
map :city, String
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
GroupRepository = Preserves.repository(model: Group) do
|
27
|
+
mapping do
|
28
|
+
map :name, String
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
UserRepository = Preserves.repository(model: User) do
|
33
|
+
mapping do
|
34
|
+
primary_key 'username'
|
35
|
+
map id: 'username'
|
36
|
+
map :age, Integer
|
37
|
+
has_many :addresses, repository: AddressRepository, foreign_key: 'username'
|
38
|
+
belongs_to :group, repository: GroupRepository
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
describe "Repository" do
|
44
|
+
|
45
|
+
subject(:repository) { UserRepository }
|
46
|
+
|
47
|
+
describe "executing a query" do
|
48
|
+
let(:query) { repository.send(:query, "INSERT INTO users (username, name, age) VALUES ($1, $2, $3)", 'booch', 'Craig', 43) }
|
49
|
+
|
50
|
+
# This can't be done with let(), because we don't want to cache it.
|
51
|
+
def number_of_rows_in_user_table
|
52
|
+
Preserves::SQL.connection(dbname: "preserves_test").exec("SELECT COUNT(*) FROM users")[0]["count"].to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
it "hits the database" do
|
56
|
+
expect{ query }.to change{ number_of_rows_in_user_table }
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns the number of rows processed" do
|
60
|
+
expect(query.size).to eq(1)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "selecting results from a query to an object" do
|
65
|
+
|
66
|
+
describe "when DB has 0 users" do
|
67
|
+
let(:selection) { repository.send(:select, "SELECT username AS id FROM users") }
|
68
|
+
|
69
|
+
it "works when restricting with `only`" do
|
70
|
+
expect(selection.only).to eq(nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "raises an exception when restricting with `only!`" do
|
74
|
+
expect{ selection.only! }.to raise_exception("expected exactly 1 result")
|
75
|
+
end
|
76
|
+
|
77
|
+
it "raises an exception when restricting with `first!`" do
|
78
|
+
expect{ selection.first! }.to raise_exception("expected at least 1 result")
|
79
|
+
end
|
80
|
+
|
81
|
+
it "raises an exception when restricting with `last!`" do
|
82
|
+
expect{ selection.last! }.to raise_exception("expected at least 1 result")
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "when DB has 1 user" do
|
88
|
+
before do
|
89
|
+
repository.send(:query, "INSERT INTO users (username, name, age) VALUES ('booch', 'Craig', 43)")
|
90
|
+
end
|
91
|
+
|
92
|
+
let(:selection) { repository.send(:select, "SELECT username AS id FROM users") }
|
93
|
+
|
94
|
+
it "returns a set of 1 User object" do
|
95
|
+
expect(selection.size).to eq(1)
|
96
|
+
expect(selection.first.class).to eq(User)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "sets the attributes on the object" do
|
100
|
+
expect(selection.first.id).to eq("booch")
|
101
|
+
end
|
102
|
+
|
103
|
+
it "works when restricting with `only`" do
|
104
|
+
expect(selection.only.id).to eq("booch")
|
105
|
+
end
|
106
|
+
|
107
|
+
it "works when restricting with `only!`" do
|
108
|
+
expect(selection.only!.id).to eq("booch")
|
109
|
+
end
|
110
|
+
|
111
|
+
it "works when restricting with `first!`" do
|
112
|
+
expect(selection.first!.id).to eq("booch")
|
113
|
+
end
|
114
|
+
|
115
|
+
it "works when restricting with `last!`" do
|
116
|
+
expect(selection.last!.id).to eq("booch")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "when DB has 2 users" do
|
121
|
+
|
122
|
+
before do
|
123
|
+
repository.send(:query, "INSERT INTO users (username, name, age) VALUES ('booch', 'Craig', 43)")
|
124
|
+
repository.send(:query, "INSERT INTO users (username, name, age) VALUES ('beth', 'Beth', 39)")
|
125
|
+
end
|
126
|
+
|
127
|
+
let(:selection) { repository.send(:select, "SELECT username AS id FROM users") }
|
128
|
+
|
129
|
+
it "returns a set of 2 User objects" do
|
130
|
+
expect(selection.size).to eq(2)
|
131
|
+
expect(selection.first.class).to eq(User)
|
132
|
+
expect(selection.last.class).to eq(User)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "sets the attributes on the objects" do
|
136
|
+
expect(selection.first.id).to eq("booch")
|
137
|
+
expect(selection.last.id).to eq("beth")
|
138
|
+
end
|
139
|
+
|
140
|
+
it "raises an exception when restricting with `only`" do
|
141
|
+
expect{ selection.only }.to raise_exception("expected only 1 result")
|
142
|
+
end
|
143
|
+
|
144
|
+
it "raises an exception when restricting with `only!`" do
|
145
|
+
expect{ selection.only! }.to raise_exception("expected exactly 1 result")
|
146
|
+
end
|
147
|
+
|
148
|
+
it "can fetch the objects by ID" do
|
149
|
+
expect(repository.fetch('booch').class).to eq(User)
|
150
|
+
expect(repository.fetch('booch').age).to eq(43)
|
151
|
+
expect(repository['booch'].class).to eq(User)
|
152
|
+
expect(repository['booch'].age).to eq(43)
|
153
|
+
expect(repository.fetch!('booch').class).to eq(User)
|
154
|
+
expect(repository.fetch!('booch').age).to eq(43)
|
155
|
+
expect{ repository.fetch!('unknown') }.to raise_exception("expected exactly 1 result")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "when mapping a field name to a different model attribute name" do
|
160
|
+
before do
|
161
|
+
repository.send(:query, "INSERT INTO users (username, name, age) VALUES ('booch', 'Craig', 43)")
|
162
|
+
end
|
163
|
+
|
164
|
+
let(:selection) { repository.send(:select, "SELECT username FROM users") }
|
165
|
+
|
166
|
+
it "sets the attribute on the object" do
|
167
|
+
expect(selection.first.id).to eq("booch")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "when mapping a field to an Integer" do
|
172
|
+
before do
|
173
|
+
repository.send(:query, "INSERT INTO users (username, name, age) VALUES ('booch', 'Craig', 43)")
|
174
|
+
end
|
175
|
+
|
176
|
+
let(:selection) { repository.send(:select, "SELECT age FROM users") }
|
177
|
+
|
178
|
+
it "sets the attribute on the object to the right type" do
|
179
|
+
expect(selection.first.age).to eq(43)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "when mapping a field to an Integer" do
|
184
|
+
before do
|
185
|
+
repository.send(:query, "INSERT INTO users (username, name, age) VALUES ('booch', 'Craig', 43)")
|
186
|
+
end
|
187
|
+
|
188
|
+
let(:selection) { repository.send(:select, "SELECT age FROM users") }
|
189
|
+
|
190
|
+
it "sets the attribute on the object to the right type" do
|
191
|
+
expect(selection.first.age).to eq(43)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "when mapping a has_many relation" do
|
196
|
+
before do
|
197
|
+
repository.send(:query, "INSERT INTO users (username, name, age) VALUES ('booch', 'Craig', 43)")
|
198
|
+
repository.send(:query, "INSERT INTO users (username, name, age) VALUES ('beth', 'Beth', 39)")
|
199
|
+
repository.send(:query, "INSERT INTO addresses (city, username) VALUES ('Overland', 'booch')")
|
200
|
+
repository.send(:query, "INSERT INTO addresses (city, username) VALUES ('Wildwood', 'booch')")
|
201
|
+
repository.send(:query, "INSERT INTO addresses (city, username) VALUES ('Ballwin', 'booch')")
|
202
|
+
repository.send(:query, "INSERT INTO addresses (city, username) VALUES ('Ballwin', 'beth')")
|
203
|
+
repository.send(:query, "INSERT INTO addresses (city, username) VALUES ('Keokuk', 'unknown')")
|
204
|
+
end
|
205
|
+
|
206
|
+
let(:address_query) { repository.send(:query, "SELECT * FROM addresses") }
|
207
|
+
let(:selection) { repository.send(:select, "SELECT * FROM users", addresses: address_query) }
|
208
|
+
|
209
|
+
it "gets the basic fields" do
|
210
|
+
expect(selection.first.id).to eq('booch')
|
211
|
+
expect(selection.first.age).to eq(43)
|
212
|
+
expect(selection.last.id).to eq('beth')
|
213
|
+
expect(selection.last.age).to eq(39)
|
214
|
+
end
|
215
|
+
|
216
|
+
it "gets all the related items" do
|
217
|
+
expect(selection.first.addresses).to_not be(nil)
|
218
|
+
expect(selection.first.addresses.size).to eq(3)
|
219
|
+
expect(selection.first.addresses.map(&:city)).to include("Overland")
|
220
|
+
expect(selection.first.addresses.map(&:city)).to include("Wildwood")
|
221
|
+
expect(selection.first.addresses.map(&:city)).to include("Ballwin")
|
222
|
+
expect(selection.last.addresses).to_not be(nil)
|
223
|
+
expect(selection.last.addresses.size).to eq(1)
|
224
|
+
expect(selection.last.addresses.map(&:city)).to include("Ballwin")
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "when mapping a belongs_to relation" do
|
230
|
+
before do
|
231
|
+
repository.send(:query, "INSERT INTO groups (id, name) VALUES (1, 'admin')")
|
232
|
+
repository.send(:query, "INSERT INTO groups (id, name) VALUES (2, 'users')")
|
233
|
+
repository.send(:query, "INSERT INTO users (username, name, age, group_id) VALUES ('booch', 'Craig', 43, 1)")
|
234
|
+
repository.send(:query, "INSERT INTO users (username, name, age, group_id) VALUES ('beth', 'Beth', 39, 2)")
|
235
|
+
end
|
236
|
+
|
237
|
+
let(:group_query) { repository.send(:query, "SELECT * FROM groups") }
|
238
|
+
let(:selection) { repository.send(:select, "SELECT * FROM users", group: group_query) }
|
239
|
+
|
240
|
+
it "gets the basic fields" do
|
241
|
+
expect(selection.first.id).to eq('booch')
|
242
|
+
expect(selection.first.age).to eq(43)
|
243
|
+
expect(selection.last.id).to eq('beth')
|
244
|
+
expect(selection.last.age).to eq(39)
|
245
|
+
end
|
246
|
+
|
247
|
+
it "gets all the related items" do
|
248
|
+
expect(selection.first.group).to_not be(nil)
|
249
|
+
expect(selection.first.group).to be_a(Group)
|
250
|
+
expect(selection.first.group.name).to eq("admin")
|
251
|
+
expect(selection.last.group).to_not be(nil)
|
252
|
+
expect(selection.last.group).to be_a(Group)
|
253
|
+
expect(selection.last.group.name).to eq("users")
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|