Skip to content

Commit b1fde0b

Browse files
committed
feat: add Converters::EnumArray
1 parent c240bb5 commit b1fde0b

File tree

7 files changed

+108
-9
lines changed

7 files changed

+108
-9
lines changed

spec/converters/enum_array_spec.cr

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require "../../spec_helper"
2+
require "../../../src/core/converters/enum_array"
3+
4+
require "pg"
5+
6+
db = DB.open(ENV["DATABASE_URL"] || raise "No DATABASE_URL is set!")
7+
8+
enum EnumArraySpecEnum
9+
Foo
10+
Bar
11+
Baz
12+
end
13+
14+
describe Core::Converters::EnumArray do
15+
db.query_each("SELECT * FROM enum_arrays") do |rs|
16+
it "returns array of enums from existing values" do
17+
converted = Core::Converters::EnumArray(EnumArraySpecEnum, Int16).from_rs(rs)
18+
converted.should eq [EnumArraySpecEnum::Bar, EnumArraySpecEnum::Baz]
19+
end
20+
21+
it "returns Nil for NULL value" do
22+
converted = Core::Converters::EnumArray(EnumArraySpecEnum, Int32).from_rs(rs)
23+
converted.should be_nil
24+
end
25+
end
26+
end

spec/migration.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ DROP TABLE IF EXISTS posts;
44
DROP TABLE IF EXISTS users;
55
DROP TABLE IF EXISTS pg_numeric;
66
DROP TABLE IF EXISTS enums;
7+
DROP TABLE IF EXISTS enum_arrays;
78

89
CREATE TABLE users(
910
id SERIAL PRIMARY KEY,
1011
referrer_id INT REFERENCES users (id),
1112
active BOOL NOT NULL DEFAULT true,
1213
role INT NOT NULL DEFAULT 0,
1314
name VARCHAR(100) NOT NULL,
15+
permissions SMALLINT[] NOT NULL DEFAULT '{0}',
1416
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1517
updated_at TIMESTAMPTZ
1618
);
@@ -38,3 +40,10 @@ CREATE TABLE enums(
3840
);
3941

4042
INSERT INTO enums (foo, bar) VALUES (1, NULL);
43+
44+
CREATE TABLE enum_arrays(
45+
foo SMALLINT[] NOT NULL,
46+
bar INT[]
47+
);
48+
49+
INSERT INTO enum_arrays (foo, bar) VALUES ('{1,2}', NULL);

spec/repository_spec.cr

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require "../src/core/repository"
66
require "../src/core/schema"
77
require "../src/core/query"
88
require "../src/core/converters/enum"
9+
require "../src/core/converters/enum_array"
910
require "../src/core/logger/io"
1011

1112
alias Repo = Core::Repository
@@ -24,6 +25,11 @@ module RepoSpec
2425
Admin
2526
end
2627

28+
enum Permission
29+
CreatePosts
30+
EditPosts
31+
end
32+
2733
schema :users do
2834
primary_key :id
2935

@@ -36,6 +42,7 @@ module RepoSpec
3642
field :active, Bool, db_default: true
3743
field :role, Role, default: Role::User, converter: Core::Converters::Enum(Role)
3844
field :name, String
45+
field :permissions, Array(Permission), converter: Core::Converters::EnumArray(Permission, Int16), db_default: true
3946

4047
field :created_at, Time, db_default: true
4148
field :updated_at, Time?
@@ -118,6 +125,7 @@ module RepoSpec
118125
user.active.should be_true
119126
user.role.should eq(User::Role::User)
120127
user.name.should eq("Test User")
128+
user.permissions.should eq [User::Permission::CreatePosts]
121129
user.created_at.should be_a(Time)
122130
user.updated_at.should eq(nil)
123131
end
@@ -225,11 +233,10 @@ module RepoSpec
225233
end
226234

227235
context "with Query instance" do
228-
update = repo.update(User.where(id: user.id).set(name: "Updated Again User"))
229-
updated_user = repo.query_one(User.last)
230-
231236
it do
232-
updated_user.name.should eq "Updated Again User"
237+
update = repo.update(User.where(id: user.id).set(permissions: [User::Permission::CreatePosts, User::Permission::EditPosts]))
238+
updated_user = repo.query_one(User.last)
239+
updated_user.permissions.should eq [User::Permission::CreatePosts, User::Permission::EditPosts]
233240
end
234241
end
235242
end

src/core/converters/enum_array.cr

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
require "../../converter"
2+
3+
module Core
4+
module Converters
5+
# Allows to represent integer array values as Array of Enums in models. You must specify which `Int` you use in the database schema (e.g. `SMALLINT` stays for `Int16`, `INT` for `Int32`).
6+
#
7+
# TODO: Remove obstructing `IntClass` requirement. See https://github.com/will/crystal-pg/issues/150
8+
#
9+
# ```
10+
# # SQL:
11+
# # table users
12+
# # column role SMALLINT[]
13+
#
14+
# require "core/converters/enum_array"
15+
#
16+
# class User
17+
# include Core::Schema
18+
#
19+
# enum Permission
20+
# CreatePosts
21+
# EditPosts
22+
# end
23+
#
24+
# schema do
25+
# field :permissions, Array(Permission), converter: Core::Converters::EnumArray(Permission, Int16)
26+
# end
27+
# end
28+
#
29+
# user.permissions # => [User::Permission::CreatePosts]
30+
# user.insert # => INSERT INTO users (permissions) VALUES('{1}')
31+
# ```
32+
class EnumArray(EnumClass, IntClass) < Converter(Array(Enum))
33+
def self.from_rs(rs)
34+
values = rs.read(Array(IntClass) | Nil)
35+
values.try &.map { |v| EnumClass.new(v.to_i32) }
36+
end
37+
38+
def self.to_db(enum _enum : Array(EnumClass))
39+
_enum.map(&.value)
40+
end
41+
end
42+
end
43+
end

src/core/query/instance/set.cr

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,15 @@ struct Core::Query::Instance(Schema)
3434
case key
3535
{% for field in Schema::INTERNAL__CORE_FIELDS %}
3636
when {{field[:name]}}
37-
set({{field[:key].id.stringify}} + " = ?", value)
37+
{% if field[:converter] %}
38+
if value.is_a?({{field[:type]}})
39+
set({{field[:key].id.stringify}} + " = ?", {{field[:converter].id}}.to_db(value))
40+
end
41+
{% else %}
42+
if value.is_a?({{field[:type]}})
43+
set({{field[:key].id.stringify}} + " = ?", value)
44+
end
45+
{% end %}
3846
{% end %}
3947
else
4048
raise ArgumentError.new("Invalid field name #{key} for #{Schema}!")
@@ -49,7 +57,7 @@ struct Core::Query::Instance(Schema)
4957
macro append_set_clauses
5058
if set_clauses.any?
5159
query += " SET " + set_clauses.map(&.[:clause]).join(", ")
52-
params.concat(set_clauses.map(&.[:params]).flatten.compact)
60+
params.concat(set_clauses.map(&.[:params]).flat_map(&.itself).compact)
5361
end
5462
end
5563
end

src/core/schema/fields.cr

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ module Core
4848
{%
4949
converter = if options[:converter]
5050
if options[:converter].is_a?(Generic)
51-
(options[:converter].name.resolve.stringify.gsub(/\(\w+\)/, "(" + options[:converter].type_vars.first.resolve.stringify + ")")).id
51+
(options[:converter].name.resolve.stringify.gsub(/\([\w, ]+\)/, "(" + options[:converter].type_vars.map { |v| v.resolve.stringify }.join(", ") + ")")).id
5252
else
5353
options[:converter]
5454
end
@@ -60,7 +60,13 @@ module Core
6060
{% INTERNAL__CORE_FIELDS.push({
6161
name: name,
6262
type: (if _type.is_a?(Generic)
63-
_type
63+
if ("#{_type.name.resolve}" == "Array(T)")
64+
_type.name.resolve.stringify.gsub(/\([\w, ]+\)/, "(" + _type.type_vars.map do |v|
65+
v.resolve.stringify
66+
end.join(", ") + ")").id
67+
else
68+
_type
69+
end
6470
else
6571
_type.resolve
6672
end),

src/core/schema/getters.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ module Core
4343
%}
4444
\{{field[:name]}} => \{{val.id}},
4545
\{% end %}
46-
} of Symbol => \{{INTERNAL__CORE_FIELDS.map(&.[:type]).join(" | ").id}}
46+
} of Symbol => Core::Param
4747
end
4848
\{% end %}
4949

0 commit comments

Comments
 (0)