Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom field types #342

Merged
merged 6 commits into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions spec/granite/fields/uuid_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ describe "UUID creation" do
item = UUIDModel.new
item.uuid.should be_nil
item.save
item.uuid.should be_a(String)
uuid = UUID.new item.uuid!
uuid.version.to_s.should eq "V4"
uuid.variant.to_s.should eq "RFC4122"
item.uuid.should be_a(UUID)
item.uuid!.version.v4?.should be_true
item.uuid!.variant.rfc4122?.should be_true
end
end
41 changes: 36 additions & 5 deletions spec/granite_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,42 @@ require "./spec_helper"
require "logger"

describe Granite::Base do
it "can instaniate a model with default values" do
model = DefaultValues.new
model.name.should eq "Jim"
model.age.should eq 0.0
model.is_alive.should be_true
describe "instantiation" do
describe "with default values" do
it "should instaniate correctly" do
model = DefaultValues.new
model.name.should eq "Jim"
model.age.should eq 0.0
model.is_alive.should be_true
end
end

describe "with a named tuple" do
it "should instaniate correctly" do
model = DefaultValues.new name: "Fred", is_alive: false
model.name.should eq "Fred"
model.age.should eq 0.0
model.is_alive.should be_false
end
end

describe "with a hash" do
it "should instaniate correctly" do
hash = {"name" => "Bob", "age" => 3.14}
model = DefaultValues.new hash
model.name.should eq "Bob"
model.age.should eq 3.14
model.is_alive.should be_true
end
end

describe "with a UUID" do
it "should instaniate correctly" do
uuid = UUID.random
model = UUIDNaturalModel.new uuid: uuid
model.uuid.should eq uuid
end
end
end

describe Logger do
Expand Down
9 changes: 8 additions & 1 deletion spec/spec_models.cr
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,14 @@ require "uuid"
adapter {{ adapter_literal }}
table_name uuids

primary uuid : String, auto: :uuid
primary uuid : UUID, converter: Granite::Converters::UuidConverter
end

class UUIDNaturalModel < Granite::Base
adapter {{ adapter_literal }}
table_name uuids

primary uuid : UUID, converter: Granite::Converters::UuidConverter, auto: false
Blacksmoke16 marked this conversation as resolved.
Show resolved Hide resolved
end

class TodoJsonOptions < Granite::Base
Expand Down
3 changes: 2 additions & 1 deletion src/adapter/mysql.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ class Granite::Adapter::Mysql < Granite::Adapter::Base

module Schema
TYPES = {
"Float64" => "DOUBLE",
"AUTO_Int32" => "INT NOT NULL AUTO_INCREMENT",
"AUTO_Int64" => "BIGINT NOT NULL AUTO_INCREMENT",
"AUTO_UUID" => "CHAR(36)",
"Float64" => "DOUBLE",
"UUID" => "CHAR(36)",
"created_at" => "TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
"updated_at" => "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",
Expand Down
1 change: 1 addition & 0 deletions src/adapter/pg.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Granite::Adapter::Pg < Granite::Adapter::Base
"String" => "TEXT",
"AUTO_Int32" => "SERIAL",
"AUTO_Int64" => "BIGSERIAL",
"AUTO_UUID" => "UUID",
"UUID" => "UUID",
"created_at" => "TIMESTAMP",
"updated_at" => "TIMESTAMP",
Expand Down
1 change: 1 addition & 0 deletions src/adapter/sqlite.cr
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Granite::Adapter::Sqlite < Granite::Adapter::Base
TYPES = {
"AUTO_Int32" => "INTEGER NOT NULL",
"AUTO_Int64" => "INTEGER NOT NULL",
"AUTO_UUID" => "CHAR(36)",
"UUID" => "CHAR(36)",
"Int32" => "INTEGER",
"Int64" => "INTEGER",
Expand Down
1 change: 1 addition & 0 deletions src/granite/base.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require "./select"
require "./version"
require "./adapters"
require "./integrators"
require "./converters"

# Granite::Base is the base class for your model objects.
abstract class Granite::Base
Expand Down
11 changes: 11 additions & 0 deletions src/granite/converters.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Granite::Converters
Blacksmoke16 marked this conversation as resolved.
Show resolved Hide resolved
module UuidConverter
def to_db(value : ::UUID) : Granite::Fields::Type
value.to_s
end

def from_rs(result : ::DB::ResultSet) : ::UUID
result.read(String?).try { |str| ::UUID.new str }
end
end
end
8 changes: 5 additions & 3 deletions src/granite/fields.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ require "json"

module Granite::Fields
alias SupportedArrayTypes = Array(String) | Array(Int16) | Array(Int32) | Array(Int64) | Array(Float32) | Array(Float64) | Array(Bool)
alias Type = DB::Any | SupportedArrayTypes
alias Type = DB::Any | SupportedArrayTypes | UUID
TIME_FORMAT_REGEX = /\d{4,}-\d{2,}-\d{2,}\s\d{2,}:\d{2,}:\d{2,}/

macro included
Expand Down Expand Up @@ -86,9 +86,9 @@ module Granite::Fields
disable_granite_docs? def content_values
parsed_params = [] of Type
{% for name, options in CONTENT_FIELDS %}
parsed_params << {{name.id}}
parsed_params << {% if options[:converter] %} {{options[:converter]}}.to_db {{name.id}} {% else %} {{name.id}} {% end %}
{% end %}
return parsed_params
parsed_params
end

disable_granite_docs? def to_h
Expand Down Expand Up @@ -167,6 +167,8 @@ module Granite::Fields
end
{% elsif type.resolve <= Array %}
@{{_name.id}} = value.as({{type.id}})
{% elsif type.id == UUID.id %}
@{{_name.id}} = value.as({{type.id}})
{% else %}
@{{_name.id}} = value.to_s
{% end %}
Expand Down
2 changes: 1 addition & 1 deletion src/granite/querying.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module Granite::Querying
# Loading from DB means existing records.
@new_record = false
\{% for name, options in FIELDS %}
self.\{{name.id}} = result.read(Union(\{{options[:type].id}} | Nil))
self.\{{name.id}} = \{% if options[:converter] %} \{{options[:converter].id}}.from_rs result \{% else %} result.read(Union(\{{options[:type].id}} | Nil)) \{% end %}
\{% if options[:type].id == "Time".id %}
self.\{{name.id}} = self.\{{name.id}}.not_nil!.in(Granite.settings.default_timezone) if self.\{{name.id}}
\{% end %}
Expand Down
20 changes: 10 additions & 10 deletions src/granite/transactions.cr
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module Granite::Transactions
create(args.to_h)
end

disable_granite_docs? def create(args : Hash(Symbol | String, DB::Any))
disable_granite_docs? def create(args : Hash(Symbol | String, Granite::Fields::Type))
instance = new
instance.set_attributes(args)
instance.save
Expand All @@ -21,7 +21,7 @@ module Granite::Transactions
create!(args.to_h)
end

disable_granite_docs? def create!(args : Hash(Symbol | String, DB::Any))
disable_granite_docs? def create!(args : Hash(Symbol | String, Granite::Fields::Type))
instance = create(args)

if instance.errors.any?
Expand Down Expand Up @@ -98,15 +98,15 @@ module Granite::Transactions
@{{primary_name}} = @@adapter.insert(@@table_name, fields, params, lastval: "{{primary_name}}").to_i32
{% elsif primary_type.id == "Int64" && primary_auto == true %}
@{{primary_name}} = @@adapter.insert(@@table_name, fields, params, lastval: "{{primary_name}}")
{% elsif primary_auto == true %}
{% raise "Failed to define #{@type.name}#save: Primary key must be Int(32|64), or set `auto: false` for natural keys.\n\n primary #{primary_name} : #{primary_type}, auto: false\n" %}
{% else %}
{% if primary_auto == :uuid %}
_uuid = UUID.random.to_s
{% elsif primary_type.id == "UUID" && primary_auto == true %}
_uuid = UUID.random
@{{primary_name}} = _uuid
params << _uuid
fields << "{{primary_name}}"
{% end %}
@@adapter.insert(@@table_name, fields, params, lastval: nil)
{% elsif primary_auto == true %}
{% raise "Failed to define #{@type.name}#save: Primary key must be Int(32|64) or UUID, or set `auto: false` for natural keys.\n\n primary #{primary_name} : #{primary_type}, auto: false\n" %}
{% else %}
if @{{primary_name}}
@@adapter.insert(@@table_name, fields, params, lastval: nil)
else
Expand Down Expand Up @@ -183,7 +183,7 @@ module Granite::Transactions
update(args.to_h)
end

disable_granite_docs? def update(args : Hash(Symbol | String, DB::Any))
disable_granite_docs? def update(args : Hash(Symbol | String, Granite::Fields::Type))
set_attributes(args)

save
Expand All @@ -193,7 +193,7 @@ module Granite::Transactions
update!(args.to_h)
end

disable_granite_docs? def update!(args : Hash(Symbol | String, DB::Any))
disable_granite_docs? def update!(args : Hash(Symbol | String, Granite::Fields::Type))
set_attributes(args)

save!
Expand Down