Skip to content

Commit 65f1c7a

Browse files
committed
feat: Repository is non-generic
Closes #3
1 parent 8b73b43 commit 65f1c7a

File tree

3 files changed

+53
-46
lines changed

3 files changed

+53
-46
lines changed

README.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,26 +87,24 @@ end
8787
8888
db = DB.open(ENV["DATABASE_URL"])
8989
query_logger = Core::QueryLogger.new(STDOUT)
90-
91-
user_repo = Core::Repository(User).new(db, query_logger)
92-
post_repo = Core::Repository(Post).new(db, query_logger)
90+
repo = Core::Repository.new(db, query_logger)
9391
9492
user = User.new(name: "Fo")
9593
user.valid? # => false
9694
user.errors # => [{:name => "length must be >= 3"}]
9795
user.name = "Foo"
9896
user.valid? # => true
9997
100-
user.id = user_repo.insert(user) # See ^1
98+
user.id = repo.insert(user) # See ^1
10199
# INSERT INTO users (name, created_at) VALUES ($1, $2) RETURNING id
102100
103101
post = Post.new(author: user, content: "Foo Bar")
104-
post.id = post_repo.insert(post) # See ^1
102+
post.id = repo.insert(post) # See ^1
105103
# INSERT INTO posts (author_id, content, created_at) VALUES ($1, $2, $3) RETURNING id
106104
107105
alias Query = Core::Query
108106
109-
posts = post_repo.query(Query(Post).where(author: user))
107+
posts = repo.query(Query(Post).where(author: user))
110108
# SELECT * FROM posts WHERE author_id = $1
111109
112110
posts.first.content # => "Foo Bar"
@@ -116,7 +114,7 @@ query = Query(User)
116114
.select(:*, :"COUNT(posts.id) AS posts_count")
117115
.group_by(%i(users.id posts.id))
118116
.one
119-
user = user_repo.query(query).first
117+
user = repo.query(query).first
120118
# SELECT *, COUNT (posts.id) AS posts_count
121119
# FROM users JOIN posts AS posts ON posts.author_id = users.id
122120
# GROUP BY users.id, posts.id LIMIT 1
@@ -125,10 +123,10 @@ user.posts_count # => 1
125123
126124
user.name = "Bar"
127125
user.changes # => {:name => "Bar"}
128-
user_repo.update(user)
126+
repo.update(user)
129127
# UPDATE users SET name = $1 WHERE id = $2 RETURNING id
130128
131-
post_repo.delete(posts.first)
129+
repo.delete(posts.first)
132130
# DELETE FROM posts WHERE id = $1
133131
```
134132

spec/repository_spec.cr

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ db = DB.open(ENV["DATABASE_URL"] || raise "No DATABASE_URL is set!")
55
query_logger = Core::QueryLogger.new(nil)
66

77
describe Repo do
8-
user_repo = Repo(User).new(db, query_logger)
9-
post_repo = Repo(Post).new(db, query_logger)
8+
repo = Repo.new(db, query_logger)
109

1110
user_created_at = uninitialized Time
1211

1312
describe "#insert" do
1413
user = User.new(name: "Test User")
15-
result = user_repo.insert(user)
14+
result = repo.insert(user)
1615
query = Query(User).last
1716

1817
it "sets created_at field" do
@@ -29,12 +28,12 @@ describe Repo do
2928
user.id = db.scalar(query.select(:id).to_s).as(Int32)
3029

3130
post = Post.new(author: user, content: "Some content")
32-
post_repo.insert(post).should be_truthy
31+
repo.insert(post).should be_truthy
3332
end
3433

3534
pending "returns fresh id" do
3635
previous_id = db.scalar(query.select(:id).to_s).as(Int32)
37-
user_repo.insert(user).should eq(previous_id + 1)
36+
repo.insert(user).should eq(previous_id + 1)
3837
end
3938
end
4039

@@ -46,7 +45,7 @@ describe Repo do
4645
.order_by(:"users.id DESC")
4746
.limit(1)
4847

49-
user = user_repo.query(complex_query).first
48+
user = repo.query(complex_query).first
5049

5150
it "returns a valid instance" do
5251
user.id.should be_a(Int32)
@@ -59,28 +58,28 @@ describe Repo do
5958

6059
pending "handles DB errors" do
6160
expect_raises do
62-
user_repo.query("INVALID QUERY")
61+
repo.query("INVALID QUERY")
6362
end
6463
end
6564
end
6665

6766
describe "#update" do
68-
user = user_repo.query(Query(User).last).first
67+
user = repo.query(Query(User).last).first
6968

7069
it "ignores empty changes" do
71-
user_repo.update(user).should eq nil
70+
repo.update(user).should eq nil
7271
end
7372

7473
pending "handles DB errors" do
7574
user.id = nil
7675
expect_raises do
77-
user_repo.update(user)
76+
repo.update(user)
7877
end
7978
end
8079

8180
user.name = "Updated User"
82-
update = user_repo.update(user)
83-
updated_user = user_repo.query(Query(User).last).first
81+
update = repo.update(user)
82+
updated_user = repo.query(Query(User).last).first
8483

8584
it "actually updates" do
8685
updated_user.name.should eq "Updated User"
@@ -92,13 +91,13 @@ describe Repo do
9291
end
9392

9493
describe "#delete" do
95-
post = post_repo.query(Query(Post).last).first
94+
post = repo.query(Query(Post).last).first
9695
post_id = post.id
97-
delete = post_repo.delete(post)
96+
delete = repo.delete(post)
9897

9998
it do
10099
delete.should be_truthy
101-
post_repo.query(Query(Post).where(id: post_id)).empty?.should eq true
100+
repo.query(Query(Post).where(id: post_id)).empty?.should eq true
102101
end
103102

104103
pending "returns an amount of affected rows" do
@@ -108,7 +107,7 @@ describe Repo do
108107
pending "handles DB errors" do
109108
# It's already deleted, so
110109
expect_raises do
111-
post_repo.delete(post)
110+
repo.delete(post)
112111
end
113112
end
114113
end

src/core/repository.cr

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require "./query_logger"
99
#
1010
# ```
1111
# logger = Core::QueryLogger.new(STDOUT)
12-
# repo = Core::Repository(User).new(db, logger)
12+
# repo = Core::Repository.new(db, logger)
1313
#
1414
# user = User.new(name: "Foo")
1515
# repo.insert(user) # TODO: [RFC] Return last inserted ID
@@ -30,7 +30,7 @@ require "./query_logger"
3030
# # DELETE FROM users WHERE id = $1
3131
# # 1.628ms
3232
# ```
33-
class Core::Repository(ModelType)
33+
class Core::Repository
3434
# :nodoc:
3535
property db
3636
# :nodoc:
@@ -44,23 +44,33 @@ class Core::Repository(ModelType)
4444
def initialize(@db : DB::Database, @query_logger : QueryLogger)
4545
end
4646

47-
# Issue a query to the `#db`. Returns an array of `Model` instances.
47+
# Query `#db` returning an array of *model* instances.
48+
#
49+
# ```
50+
# repo.query(User, "SELECT * FROM users") # => Array(User)
51+
# ```
4852
#
4953
# TODO: Handle errors (PQ::PQError)
50-
def query(query : String, *params) : Array(ModelType)
54+
def query(model : Model.class, query : String, *params) : Array
5155
query = prepare_query(query)
5256
params = prepare_params(*params) if params.any?
5357

5458
query_logger.wrap(query) do
5559
db.query_all(query, *params) do |rs|
56-
rs.read(ModelType)
60+
rs.read(model)
5761
end
5862
end
5963
end
6064

61-
# ditto
62-
def query(query : Query)
63-
query(query.to_s, query.params)
65+
# Query `#db` returning an array of model instances inherited from *query*.
66+
#
67+
# ```
68+
# repo.query(Query(User).all) # => Array(User)
69+
# ```
70+
#
71+
# TODO: Handle errors (PQ::PQError)
72+
def query(query : Query(T)) forall T
73+
query(T, query.to_s, query.params)
6474
end
6575

6676
private SQL_INSERT = <<-SQL
@@ -75,19 +85,19 @@ class Core::Repository(ModelType)
7585
# TODO: Handle errors.
7686
# TODO: Multiple inserts.
7787
# TODO: [RFC] Call `#query` and return `Model` instance instead (see https://github.com/will/crystal-pg/issues/101).
78-
def insert(instance : ModelType) : Int64
88+
def insert(instance : Model) : Int64
7989
fields = instance.db_fields.dup.tap do |f|
8090
f.each do |k, _|
81-
f[k] = now if ModelType.created_at_fields.includes?(k) && f[k].nil?
82-
f.delete(k) if k == ModelType.primary_key
91+
f[k] = now if instance.class.created_at_fields.includes?(k) && f[k].nil?
92+
f.delete(k) if k == instance.class.primary_key
8393
end
8494
end
8595

8696
query = SQL_INSERT % {
87-
table_name: ModelType.table_name,
97+
table_name: instance.class.table_name,
8898
keys: fields.keys.join(", "),
8999
values: (1..fields.size).map { "?" }.join(", "),
90-
returning: ModelType.primary_key,
100+
returning: instance.class.primary_key,
91101
}
92102

93103
query = prepare_query(query)
@@ -111,22 +121,22 @@ class Core::Repository(ModelType)
111121
# TODO: Handle errors.
112122
# TODO: Multiple updates.
113123
# TODO: [RFC] Call `#query` and return `Model` instance instead (see https://github.com/will/crystal-pg/issues/101).
114-
def update(instance : ModelType)
124+
def update(instance : Model)
115125
fields = instance.db_fields.select do |k, _|
116126
instance.changes.keys.includes?(k)
117127
end.tap do |f|
118128
f.each do |k, _|
119-
f[k] = now if ModelType.updated_at_fields.includes?(k)
129+
f[k] = now if instance.class.updated_at_fields.includes?(k)
120130
end
121131
end
122132

123133
return unless fields.any?
124134

125135
query = SQL_UPDATE % {
126-
table_name: ModelType.table_name,
136+
table_name: instance.class.table_name,
127137
set_fields: fields.keys.map { |k| k.to_s + " = ?" }.join(", "),
128-
primary_key: ModelType.primary_key, # TODO: Handle empty primary key
129-
returning: ModelType.primary_key,
138+
primary_key: instance.class.primary_key, # TODO: Handle empty primary key
139+
returning: instance.class.primary_key,
130140
}
131141

132142
query = prepare_query(query)
@@ -146,10 +156,10 @@ class Core::Repository(ModelType)
146156
#
147157
# TODO: Handle errors.
148158
# TODO: Multiple deletes.
149-
def delete(instance : ModelType)
159+
def delete(instance : Model)
150160
query = SQL_DELETE % {
151-
table_name: ModelType.table_name,
152-
primary_key: ModelType.primary_key,
161+
table_name: instance.class.table_name,
162+
primary_key: instance.class.primary_key,
153163
}
154164

155165
query = prepare_query(query)

0 commit comments

Comments
 (0)