From 1053ad4805127493231cdd8f6453535fffa1828d Mon Sep 17 00:00:00 2001 From: Blacksmoke16 Date: Thu, 3 Oct 2019 21:25:08 -0400 Subject: [PATCH 1/6] Refactor initialization to remove need for set_attributes Don't make columns nilable by default anymore No longer include *::Serializable by default --- spec/adapter/adapters_spec.cr | 4 +- spec/granite/associations/belongs_to_spec.cr | 24 --- spec/granite/columns/uuid_spec.cr | 4 +- spec/granite/querying/query_builder_spec.cr | 6 +- spec/granite/transactions/save_spec.cr | 4 +- spec/granite/transactions/update_spec.cr | 4 +- spec/granite/validations/validator_spec.cr | 6 +- spec/granite_spec.cr | 214 +------------------ spec/spec_models.cr | 108 +++++----- src/granite.cr | 2 - src/granite/base.cr | 40 +++- src/granite/columns.cr | 70 +----- src/granite/querying.cr | 15 +- src/granite/transactions.cr | 30 ++- src/granite/type.cr | 34 --- 15 files changed, 124 insertions(+), 441 deletions(-) diff --git a/spec/adapter/adapters_spec.cr b/spec/adapter/adapters_spec.cr index 3b4cc5c2..eeba3262 100644 --- a/spec/adapter/adapters_spec.cr +++ b/spec/adapter/adapters_spec.cr @@ -3,11 +3,11 @@ require "../spec_helper" class Foo < Granite::Base connection sqlite - column id : Int64, primary: true + column id : Int64?, primary: true end class Bar < Granite::Base - column id : Int64, primary: true + column id : Int64?, primary: true end describe Granite::Connections do diff --git a/spec/granite/associations/belongs_to_spec.cr b/spec/granite/associations/belongs_to_spec.cr index df8d14ad..ca2d8ba8 100644 --- a/spec/granite/associations/belongs_to_spec.cr +++ b/spec/granite/associations/belongs_to_spec.cr @@ -66,30 +66,6 @@ describe "belongs_to" do book.publisher.name.should eq "Amber Framework" end - it "supports json_options" do - publisher = Company.new - publisher.name = "Amber Framework" - publisher.save - - book = Book.new - book.name = "Introduction to Granite" - book.publisher = publisher - book.save - book.to_json.should eq %({"id":#{book.id},"name":"Introduction to Granite"}) - end - - it "supports yaml_options" do - publisher = Company.new - publisher.name = "Amber Framework" - publisher.save - - book = Book.new - book.name = "Introduction to Granite" - book.publisher = publisher - book.save - book.to_yaml.should eq %(---\nid: #{book.id}\nname: Introduction to Granite\n) - end - it "provides a method to retrieve parent object that will raise if record is not found" do book = Book.new book.name = "Introduction to Granite" diff --git a/spec/granite/columns/uuid_spec.cr b/spec/granite/columns/uuid_spec.cr index b6b00e0f..3c2067af 100644 --- a/spec/granite/columns/uuid_spec.cr +++ b/spec/granite/columns/uuid_spec.cr @@ -6,7 +6,7 @@ describe "UUID creation" do item.uuid.should be_nil item.save item.uuid.should be_a(UUID) - item.uuid!.version.v4?.should be_true - item.uuid!.variant.rfc4122?.should be_true + item.uuid.try(&.version.v4?).should be_true + item.uuid.try(&.variant.rfc4122?).should be_true end end diff --git a/spec/granite/querying/query_builder_spec.cr b/spec/granite/querying/query_builder_spec.cr index 146f5f66..647485cb 100644 --- a/spec/granite/querying/query_builder_spec.cr +++ b/spec/granite/querying/query_builder_spec.cr @@ -26,10 +26,10 @@ describe Granite::Query::BuilderMethods do {% if env("CURRENT_ADAPTER") == "sqlite" %} it "correctly queries bool fields" do Review.clear - Review.create(name: "one", published: 1) - review2 = Review.create(name: "two", published: 0) + Review.create(name: "one", published: true) + review2 = Review.create(name: "two", published: false) - found = Review.where(published: [0]).select + found = Review.where(published: [false]).select found.size.should eq 1 found[0].id.should eq review2.id diff --git a/spec/granite/transactions/save_spec.cr b/spec/granite/transactions/save_spec.cr index b062ad59..c08d56bd 100644 --- a/spec/granite/transactions/save_spec.cr +++ b/spec/granite/transactions/save_spec.cr @@ -15,7 +15,7 @@ describe "#save" do parent.persisted?.should be_false end - it "does not save a model with type conversion errors" do + pending "does not save a model with type conversion errors" do model = Comment.new(articleid: "foo") model.errors.size.should eq 1 model.save.should be_false @@ -46,7 +46,7 @@ describe "#save" do parent.name.should eq "Test Parent" end - it "does not update when the conflicted primary key is given to the new record" do + pending "does not update when the conflicted primary key is given to the new record" do parent1 = Parent.new parent1.name = "Test Parent" parent1.save.should be_true diff --git a/spec/granite/transactions/update_spec.cr b/spec/granite/transactions/update_spec.cr index d0f3e348..e2577a30 100644 --- a/spec/granite/transactions/update_spec.cr +++ b/spec/granite/transactions/update_spec.cr @@ -1,6 +1,6 @@ require "../../spec_helper" -describe "#update" do +pending "#update" do it "updates an object" do parent = Parent.new(name: "New Parent") parent.save! @@ -43,7 +43,7 @@ describe "#update" do end end -describe "#update!" do +pending "#update!" do it "updates an object" do parent = Parent.new(name: "New Parent") parent.save! diff --git a/spec/granite/validations/validator_spec.cr b/spec/granite/validations/validator_spec.cr index acaefdc3..354c71f5 100644 --- a/spec/granite/validations/validator_spec.cr +++ b/spec/granite/validations/validator_spec.cr @@ -5,7 +5,7 @@ require "../../spec_helper" class NameTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? validate :name, "cannot be blank", ->(s : NameTest) do @@ -16,7 +16,7 @@ require "../../spec_helper" class EmailTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column email : String? validate :email, "cannot be blank" do |email_test| @@ -27,7 +27,7 @@ require "../../spec_helper" class PasswordTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column password : String? column password_validation : String? diff --git a/spec/granite_spec.cr b/spec/granite_spec.cr index 4c782d58..4a397297 100644 --- a/spec/granite_spec.cr +++ b/spec/granite_spec.cr @@ -39,7 +39,7 @@ describe Granite::Base do end end - describe "with a hash" do + pending "with a hash" do it "should instaniate correctly" do model = DefaultValues.new({"name" => "Bob", "age" => 3.14}) model.name.should eq "Bob" @@ -59,7 +59,7 @@ describe Granite::Base do end end - describe "with string numeric values" do + pending "with string numeric values" do it "should instaniate correctly" do model = StringConversion.new({"user_id" => "1", "int32" => "17", "float32" => "3.14", "float" => "92342.2342342"}) @@ -102,216 +102,6 @@ describe Granite::Base do end end - describe "JSON" do - describe ".from_json" do - it "can create an object from json" do - json_str = %({"name": "json::anyReview","upvotes": 2, "sentiment": 1.23, "interest": 4.56, "published": true}) - - review = Review.from_json(json_str) - review.name.should eq "json::anyReview" - review.upvotes.should eq 2 - review.sentiment.should eq 1.23_f32 - review.interest.should eq 4.56 - review.published.should eq true - review.created_at.should be_nil - end - - it "can create an array of objects from json" do - json_str = %([{"name": "json1","upvotes": 2, "sentiment": 1.23, "interest": 4.56, "published": true},{"name": "json2","upvotes": 0, "sentiment": 5.00, "interest": 6.99, "published": false}]) - - review = Array(Review).from_json(json_str) - review[0].name.should eq "json1" - review[0].upvotes.should eq 2 - review[0].sentiment.should eq 1.23_f32 - review[0].interest.should eq 4.56 - review[0].published.should be_true - review[0].created_at.should be_nil - - review[1].name.should eq "json2" - review[1].upvotes.should eq 0 - review[1].sentiment.should eq 5.00_f32 - review[1].interest.should eq 6.99 - review[1].published.should be_false - review[1].created_at.should be_nil - end - - it "works with after_initialize" do - model = AfterInit.from_json(%({"name": "after_initialize"})) - - model.name.should eq "after_initialize" - model.priority.should eq 1000 - end - - describe "with default values" do - it "correctly applies values" do - model = DefaultValues.from_json(%({"name": "Bob"})) - model.name.should eq "Bob" - model.age.should eq 0.0 - model.is_alive.should be_true - end - end - end - - describe "#to_json" do - it "emits nil values when told" do - t = TodoEmitNull.new(name: "test todo", priority: 20) - result = %({"id":null,"name":"test todo","priority":20,"created_at":null,"updated_at":null}) - - t.to_json.should eq result - end - - it "does not emit nil values by default" do - t = Todo.new(name: "test todo", priority: 20) - result = %({"name":"test todo","priority":20}) - - t.to_json.should eq result - end - - it "works with array of models" do - todos = [ - Todo.new(name: "todo 1", priority: 1), - Todo.new(name: "todo 2", priority: 2), - Todo.new(name: "todo 3", priority: 3), - ] - - collection = todos.to_json - collection.should eq %([{"name":"todo 1","priority":1},{"name":"todo 2","priority":2},{"name":"todo 3","priority":3}]) - end - end - - context "with json_options" do - model = TodoJsonOptions.from_json(%({"task_name": "The Task", "priority": 9000})) - it "should deserialize correctly" do - model.name.should eq "The Task" - model.priority.should be_nil - end - - it "should serialize correctly" do - model.to_json.should eq %({"task_name":"The Task"}) - end - - describe "when using timestamp fields" do - TodoJsonOptions.import([ - TodoJsonOptions.new(name: "first todo", priority: 200), - TodoJsonOptions.new(name: "second todo", priority: 500), - TodoJsonOptions.new(name: "third todo", priority: 300), - ]) - - it "should serialize correctly" do - todos = TodoJsonOptions.order(id: :asc).select - todos[0].to_json.should eq %({"id":1,"task_name":"first todo","posted":"#{Time::Format::RFC_3339.format(todos[0].created_at!)}"}) - todos[1].to_json.should eq %({"id":2,"task_name":"second todo","posted":"#{Time::Format::RFC_3339.format(todos[1].created_at!)}"}) - todos[2].to_json.should eq %({"id":3,"task_name":"third todo","posted":"#{Time::Format::RFC_3339.format(todos[2].created_at!)}"}) - end - end - end - end - - describe "YAML" do - describe ".from_yaml" do - it "can create an object from YAML" do - yaml_str = %(---\nname: yaml::anyReview\nupvotes: 2\nsentiment: 1.23\ninterest: 4.56\npublished: true) - - review = Review.from_yaml(yaml_str) - review.name.should eq "yaml::anyReview" - review.upvotes.should eq 2 - review.sentiment.should eq 1.23.to_f32 - review.interest.should eq 4.56 - review.published.should eq true - review.created_at.should be_nil - end - - it "can create an array of objects from YAML" do - yaml_str = "---\n- name: yaml1\n upvotes: 2\n sentiment: 1.23\n interest: 4.56\n published: true\n- name: yaml2\n upvotes: 0\n sentiment: !!float 5\n interest: 6.99\n published: false" - - review = Array(Review).from_yaml(yaml_str) - review[0].name.should eq "yaml1" - review[0].upvotes.should eq 2 - review[0].sentiment.should eq 1.23.to_f32 - review[0].interest.should eq 4.56 - review[0].published.should be_true - review[0].created_at.should be_nil - - review[1].name.should eq "yaml2" - review[1].upvotes.should eq 0 - review[1].sentiment.should eq 5.00.to_f32 - review[1].interest.should eq 6.99 - review[1].published.should be_false - review[1].created_at.should be_nil - end - - it "works with after_initialize" do - model = AfterInit.from_yaml(%(---\nname: after_initialize)) - - model.name.should eq "after_initialize" - model.priority.should eq 1000 - end - - describe "with default values" do - it "correctly applies values" do - model = DefaultValues.from_yaml(%(---\nname: Bob)) - model.name.should eq "Bob" - model.age.should eq 0.0 - model.is_alive.should be_true - end - end - end - - describe "#to_yaml" do - it "emits nil values when told" do - t = TodoEmitNull.new(name: "test todo", priority: 20) - result = %(---\nid: \nname: test todo\npriority: 20\ncreated_at: \nupdated_at: \n) - - t.to_yaml.should eq result - end - - it "does not emit nil values by default" do - t = Todo.new(name: "test todo", priority: 20) - result = %(---\nname: test todo\npriority: 20\n) - - t.to_yaml.should eq result - end - - it "works with array of models" do - todos = [ - Todo.new(name: "todo 1", priority: 1), - Todo.new(name: "todo 2", priority: 2), - Todo.new(name: "todo 3", priority: 3), - ] - - collection = todos.to_yaml - collection.should eq %(---\n- name: todo 1\n priority: 1\n- name: todo 2\n priority: 2\n- name: todo 3\n priority: 3\n) - end - end - - context "with yaml_options" do - model = TodoYamlOptions.from_yaml(%(---\ntask_name: The Task\npriority: 9000)) - it "should deserialize correctly" do - model.name.should eq "The Task" - model.priority.should be_nil - end - - it "should serialize correctly" do - model.to_yaml.should eq %(---\ntask_name: The Task\n) - end - - describe "when using timestamp fields" do - TodoYamlOptions.import([ - TodoYamlOptions.new(name: "first todo", priority: 200), - TodoYamlOptions.new(name: "second todo", priority: 500), - TodoYamlOptions.new(name: "third todo", priority: 300), - ]) - - it "should serialize correctly" do - todos = TodoYamlOptions.order(id: :asc).select - todos[0].to_yaml.should eq %(---\nid: 1\ntask_name: first todo\nposted: #{Time::Format::YAML_DATE.format(todos[0].created_at!)}\n) - todos[1].to_yaml.should eq %(---\nid: 2\ntask_name: second todo\nposted: #{Time::Format::YAML_DATE.format(todos[1].created_at!)}\n) - todos[2].to_yaml.should eq %(---\nid: 3\ntask_name: third todo\nposted: #{Time::Format::YAML_DATE.format(todos[2].created_at!)}\n) - end - end - end - end - describe "#to_h" do it "convert object to hash" do t = Todo.new(name: "test todo", priority: 20) diff --git a/spec/spec_models.cr b/spec/spec_models.cr index 215472d9..10734f8c 100644 --- a/spec/spec_models.cr +++ b/spec/spec_models.cr @@ -12,7 +12,7 @@ end connection {{ adapter_literal }} table parents - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? timestamps @@ -27,7 +27,7 @@ end connection {{ adapter_literal }} table teachers - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? has_many :klasses, class_name: Klass @@ -37,7 +37,7 @@ end connection {{ adapter_literal }} table students - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? has_many :enrollments, class_name: Enrollment @@ -48,7 +48,7 @@ end connection {{ adapter_literal }} table klasses - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? belongs_to teacher : Teacher @@ -61,7 +61,7 @@ end connection {{ adapter_literal }} table enrollments - column id : Int64, primary: true + column id : Int64?, primary: true belongs_to :student belongs_to :klass @@ -71,7 +71,7 @@ end connection {{ adapter_literal }} table schools - column custom_id : Int64, primary: true + column custom_id : Int64?, primary: true column name : String? end @@ -79,7 +79,7 @@ end connection {{ adapter_literal }} table users - column id : Int64, primary: true + column id : Int64?, primary: true column email : String? has_one :profile @@ -89,7 +89,7 @@ end connection {{ adapter_literal }} table characters - column character_id : Int32, primary: true + column character_id : Int32?, primary: true, auto: false column name : String end @@ -97,7 +97,7 @@ end connection {{ adapter_literal }} table couriers - column courier_id : Int32, primary: true, auto: false + column courier_id : Int32?, primary: true, auto: false column issuer_id : Int32 belongs_to service : CourierService, primary_key: "owner_id" @@ -108,7 +108,7 @@ end connection {{ adapter_literal }} table services - column owner_id : Int64, primary: true, auto: false + column owner_id : Int64?, primary: true, auto: false column name : String has_many :couriers, class_name: Courier, foreign_key: "service_id" @@ -118,7 +118,7 @@ end connection {{ adapter_literal }} table profiles - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? belongs_to :user @@ -128,7 +128,7 @@ end connection {{ adapter_literal }} table nation_counties - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? end @@ -136,7 +136,7 @@ end connection {{ adapter_literal }} table reviews - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? column downvotes : Int32? column upvotes : Int64? @@ -150,14 +150,14 @@ end connection {{ adapter_literal }} table empties - column id : Int64, primary: true + column id : Int64?, primary: true end class ReservedWord < Granite::Base connection {{ adapter_literal }} table "select" - column id : Int64, primary: true + column id : Int64?, primary: true column all : String? end @@ -165,7 +165,7 @@ end connection {{ adapter_literal }} table callbacks - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? property history : IO::Memory = IO::Memory.new @@ -182,7 +182,7 @@ end connection {{ adapter_literal }} table callbacks_with_abort - column abort_at : String, primary: true, auto: false + column abort_at : String?, primary: true, auto: false column do_abort : Bool? column name : String? @@ -200,7 +200,7 @@ end connection {{ adapter_literal }} table kvs - column k : String, primary: true, auto: false + column k : String?, primary: true, auto: false column v : String? end @@ -208,7 +208,7 @@ end connection {{ adapter_literal }} table people - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? end @@ -216,7 +216,7 @@ end connection {{ adapter_literal }} table companies - column id : Int32, primary: true + column id : Int32?, primary: true column name : String? end @@ -224,7 +224,7 @@ end connection {{ adapter_literal }} table books - column id : Int32, primary: true + column id : Int32?, primary: true column name : String? @[JSON::Field(ignore: true)] @@ -238,7 +238,7 @@ end connection {{ adapter_literal }} table book_reviews - column id : Int32, primary: true + column id : Int32?, primary: true column body : String? belongs_to book : Book, foreign_key: book_id : Int32? @@ -248,7 +248,7 @@ end connection {{ adapter_literal }} table items - column item_id : String, primary: true, auto: false + column item_id : String?, primary: true, auto: false column item_name : String? before_create :generate_uuid @@ -262,7 +262,7 @@ end connection {{ adapter_literal }} table non_auto_default_pk - column id : Int64, primary: true, auto: false + column id : Int64?, primary: true, auto: false column name : String? end @@ -270,7 +270,7 @@ end connection {{ adapter_literal }} table non_auto_custom_pk - column custom_id : Int64, primary: true, auto: false + column custom_id : Int64?, primary: true, auto: false column name : String? end @@ -278,7 +278,7 @@ end connection {{ adapter_literal }} table articles - column id : Int64, primary: true + column id : Int64?, primary: true column articlebody : String? end @@ -286,7 +286,7 @@ end connection {{ adapter_literal }} table comments - column id : Int64, primary: true + column id : Int64?, primary: true column commentbody : String? column articleid : Int64? end @@ -294,7 +294,7 @@ end class SongThread < Granite::Base connection {{ env("CURRENT_ADAPTER").id }} - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? end @@ -302,7 +302,7 @@ end connection {{ env("CURRENT_ADAPTER").id }} table custom_table_name - column custom_primary_key : Int64, primary: true + column custom_primary_key : Int64?, primary: true column name : String? end @@ -312,7 +312,7 @@ end connection {{ adapter_literal }} table todos - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? column priority : Int32? timestamps @@ -322,7 +322,7 @@ end connection {{ adapter_literal }} table todos - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? column priority : Int32? timestamps @@ -332,7 +332,7 @@ end connection {{ adapter_literal }} table after_json_init - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? column priority : Int32? @@ -344,7 +344,7 @@ end class ArticleViewModel < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column articlebody : String? column commentbody : String? @@ -358,7 +358,7 @@ end class ArrayModel < Granite::Base connection {{ adapter_literal }} - column id : Int32, primary: true + column id : Int32?, primary: true column str_array : Array(String)? column i16_array : Array(Int16)? column i32_array : Array(Int32)? @@ -381,7 +381,7 @@ end connection {{ adapter_literal }} table uuids - column uuid : UUID, primary: true, converter: Granite::Converters::Uuid(String), auto: false + column uuid : UUID?, primary: true, converter: Granite::Converters::Uuid(String), auto: false column field_uuid : UUID?, converter: Granite::Converters::Uuid(String) end @@ -389,7 +389,7 @@ end connection {{ adapter_literal }} table todos_json - column id : Int64, primary: true + column id : Int64?, primary: true @[JSON::Field(key: "task_name")] column name : String? @@ -408,7 +408,7 @@ end connection {{ adapter_literal }} table todos_yaml - column id : Int64, primary: true + column id : Int64?, primary: true @[YAML::Field(key: "task_name")] column name : String? @@ -427,7 +427,7 @@ end connection {{ adapter_literal }} table defaults - column id : Int64, primary: true + column id : Int64?, primary: true column name : String = "Jim" column is_alive : Bool = true column age : Float64 = 0.0 @@ -437,7 +437,7 @@ end connection {{ adapter_literal }} table times - column id : Int64, primary: true + column id : Int64?, primary: true column test : Time? column name : String? timestamps @@ -447,7 +447,7 @@ end connection {{ adapter_literal }} table manual_column_types - column id : Int64, primary: true + column id : Int64?, primary: true column foo : UUID?, column_type: "DECIMAL(12, 10)" end @@ -455,7 +455,7 @@ end connection {{ adapter_literal }} table "event_cons" - column id : Int64, primary: true + column id : Int64?, primary: true column con_name : String column event_name : String? @@ -470,7 +470,7 @@ end belongs_to :user - column id : Int64, primary: true + column id : Int64?, primary: true column int32 : Int32 column float32 : Float32 column float : Float64 @@ -497,7 +497,7 @@ end connection {{ adapter_literal }} table enum_model - column id : Int64, primary: true + column id : Int64?, primary: true column my_enum : MyEnum?, column_type: "TEXT", converter: Granite::Converters::Enum(MyEnum, String) end @@ -506,7 +506,7 @@ end connection {{ adapter_literal }} table converters - column id : Int64, primary: true + column id : Int64?, primary: true column binary_json : MyType?, column_type: "BYTEA", converter: Granite::Converters::Json(MyType, Bytes) column string_json : MyType?, column_type: "JSON", converter: Granite::Converters::Json(MyType, JSON::Any) @@ -537,7 +537,7 @@ end connection {{ adapter_literal }} table converters - column id : Int64, primary: true + column id : Int64?, primary: true column binary_json : MyType?, column_type: "BLOB", converter: Granite::Converters::Json(MyType, Bytes) column string_json : MyType?, column_type: "TEXT", converter: Granite::Converters::Json(MyType, String) @@ -554,7 +554,7 @@ end connection {{ adapter_literal }} table converters - column id : Int64, primary: true + column id : Int64?, primary: true column binary_json : MyType?, column_type: "BLOB", converter: Granite::Converters::Json(MyType, Bytes) column string_json : MyType?, column_type: "TEXT", converter: Granite::Converters::Json(MyType, String) @@ -573,7 +573,7 @@ end class NilTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column first_name_not_nil : String? column last_name_not_nil : String? @@ -603,7 +603,7 @@ end class BlankTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column first_name_not_blank : String? column last_name_not_blank : String? @@ -621,7 +621,7 @@ end class ChoiceTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column number_symbol : Int32? column type_array_symbol : String? @@ -638,7 +638,7 @@ end class LessThanTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column int_32_lt : Int32? column float_32_lt : Float32? @@ -656,7 +656,7 @@ end class GreaterThanTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column int_32_lt : Int32? column float_32_lt : Float32? @@ -674,7 +674,7 @@ end class LengthTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column title : String? column description : String? @@ -685,7 +685,7 @@ end class PersonUniqueness < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? validate_uniqueness :name @@ -694,7 +694,7 @@ end class ExclusionTest < Granite::Base connection {{ adapter_literal }} - column id : Int64, primary: true + column id : Int64?, primary: true column name : String? validate_exclusion :name, ["test_name"] diff --git a/src/granite.cr b/src/granite.cr index f422f364..15499a3e 100644 --- a/src/granite.cr +++ b/src/granite.cr @@ -5,8 +5,6 @@ module Granite TIME_ZONE = "UTC" DATETIME_FORMAT = "%F %X%z" - alias ModelArgs = Hash(Symbol | String, Granite::Columns::Type) - annotation Column; end annotation Table; end end diff --git a/src/granite/base.cr b/src/granite/base.cr index 3a95cd97..4af30dfd 100644 --- a/src/granite/base.cr +++ b/src/granite/base.cr @@ -40,9 +40,6 @@ abstract class Granite::Base extend Select macro inherited - include JSON::Serializable - include YAML::Serializable - @@select = Container.new(table_name: table_name, fields: fields) # Returns true if this object hasn't been saved yet. @@ -60,15 +57,36 @@ abstract class Granite::Base !(new_record? || destroyed?) end - disable_granite_docs? def initialize(**args : Granite::Columns::Type) - set_attributes(args.to_h.transform_keys(&.to_s)) - end - - disable_granite_docs? def initialize(args : Granite::ModelArgs) - set_attributes(args.transform_keys(&.to_s)) - end + # Consumes the result set to set self's property values. + disable_granite_docs? def initialize(result : DB::ResultSet) : Nil + {% verbatim do %} + {% begin %} + {% for column in @type.instance_vars.select { |ivar| ivar.annotation(Granite::Column) } %} + {% ann = column.annotation(Granite::Column) %} + @{{column.id}} = {% if ann[:converter] %} {{ann[:converter]}}.from_rs result {% else %} Granite::Type.from_rs(result, {{column.type}}) {% end %} + {% end %} + {% end %} + {% end %} + end - disable_granite_docs? def initialize + disable_granite_docs? def initialize(*args, **named_args) + {% verbatim do %} + {% begin %} + {% for column, idx in @type.instance_vars.select { |ivar| (ann = ivar.annotation(Granite::Column)) && (!ann[:primary] || (ann[:primary] && ann[:auto] == false)) } %} + @{{column.id}} = if (val = args[{{idx}}]?) || (val = named_args[{{column.name.stringify}}]?) + val + else + {% if column.has_default_value? %} + {{column.default_value}} + {% elsif !column.type.nilable? %} + raise "Missing required property {{column}}" + {% else %} + nil + {% end %} + end + {% end %} + {% end %} + {% end %} end end end diff --git a/src/granite/columns.cr b/src/granite/columns.cr index 77357869..9c422f3d 100644 --- a/src/granite/columns.cr +++ b/src/granite/columns.cr @@ -23,7 +23,7 @@ module Granite::Columns end end - def content_values : Array(Granite::Columns::Type) + def content_values parsed_params = [] of Type {% for column in @type.instance_vars.select { |ivar| (ann = ivar.annotation(Granite::Column)) && !ann[:primary] } %} {% ann = column.annotation(Granite::Column) %} @@ -32,30 +32,9 @@ module Granite::Columns parsed_params end - # Consumes the result set to set self's property values. - def from_rs(result : DB::ResultSet) : Nil - {% begin %} - result.column_names.each do |col| - case col - {% for column in @type.instance_vars.select { |ivar| ivar.annotation(Granite::Column) } %} - {% ann = column.annotation(Granite::Column) %} - when {{column.name.stringify}} - @{{column.id}} = {% if ann[:converter] %} - {{ann[:converter]}}.from_rs result - {% else %} - Granite::Type.from_rs(result, {{ann[:nilable] ? column.type : column.type.union_types.reject { |t| t == Nil }.first}}) {% if column.has_default_value? && !column.default_value.nil? %} || {{column.default_value}} {% end %} - {% end %} - {% end %} - end - end - {% end %} - end - # Defines a column *decl* with the given *options*. macro column(decl, **options) {% type = decl.type %} - {% not_nilable_type = type.is_a?(Path) ? type.resolve : (type.is_a?(Union) ? type.types.reject(&.resolve.nilable?).first : (type.is_a?(Generic) ? type.resolve : type)) %} - # Raise an exception if the delc type has more than 2 union types or if it has 2 types without nil # This prevents having a column typed to String | Int32 etc. {% if type.is_a?(Union) && (type.types.size > 2 || (type.types.size == 2 && !type.types.any?(&.resolve.nilable?))) %} @@ -70,28 +49,12 @@ module Granite::Columns {% nilable = (type.is_a?(Path) ? type.resolve.nilable? : (type.is_a?(Union) ? type.types.any?(&.resolve.nilable?) : (type.is_a?(Generic) ? type.resolve.nilable? : type.nilable?))) %} - @[Granite::Column(column_type: {{column_type}}, converter: {{converter}}, auto: {{auto}}, primary: {{primary}}, nilable: {{nilable}})] - @{{decl.var}} : {{decl.type}}? {% unless decl.value.is_a? Nop %} = {{decl.value}} {% end %} - - {% if nilable || primary %} - def {{decl.var.id}}=(@{{decl.var.id}} : {{not_nilable_type}}?); end - - def {{decl.var.id}} : {{not_nilable_type}}? - @{{decl.var}} - end - - def {{decl.var.id}}! : {{not_nilable_type}} - raise NilAssertionError.new {{@type.name.stringify}} + "#" + {{decl.var.stringify}} + " cannot be nil" if @{{decl.var}}.nil? - @{{decl.var}}.not_nil! - end - {% else %} - def {{decl.var.id}}=(@{{decl.var.id}} : {{type.id}}); end - - def {{decl.var.id}} : {{type.id}} - raise NilAssertionError.new {{@type.name.stringify}} + "#" + {{decl.var.stringify}} + " cannot be nil" if @{{decl.var}}.nil? - @{{decl.var}}.not_nil! - end + {% if primary && !type.resolve.nilable? %} + {% raise "Primary key of #{@type} must be nilable" %} {% end %} + + @[Granite::Column(column_type: {{column_type}}, converter: {{converter}}, auto: {{auto}}, primary: {{primary}}, nilable: {{nilable}})] + {% if !primary || (primary && !auto) %} property {% else %} getter {% end %} {{decl.var}} : {{decl.type}} {% unless decl.value.is_a? Nop %} = {{decl.value}} {% end %} end # include created_at and updated_at that will automatically be updated @@ -116,27 +79,6 @@ module Granite::Columns fields end - def set_attributes(hash : Hash(String | Symbol, Type)) : self - {% for column in @type.instance_vars.select { |ivar| (ann = ivar.annotation(Granite::Column)) && (!ann[:primary] || (ann[:primary] && ann[:auto] == false)) } %} - if hash.has_key?({{column.stringify}}) && !hash[{{column.stringify}}].nil? - begin - val = Granite::Type.convert_type hash[{{column.stringify}}], {{column.type}} - rescue ex : ArgumentError - error = Granite::ConversionError.new({{column.name.stringify}}, ex.message) - end - - if !val.is_a? {{column.type}} - error = Granite::ConversionError.new({{column.name.stringify}}, "Expected {{column.id}} to be {{column.type}} but got #{typeof(val)}.") - else - @{{column}} = val - end - - errors << error if error - end - {% end %} - self - end - def read_attribute(attribute_name : Symbol | String) : DB::Any {% begin %} case attribute_name.to_s diff --git a/src/granite/querying.cr b/src/granite/querying.cr index 113a2e37..7b3ae668 100644 --- a/src/granite/querying.cr +++ b/src/granite/querying.cr @@ -4,9 +4,8 @@ module Granite::Querying # Entrypoint for creating a new object from a result set. def from_rs(result : DB::ResultSet) : self - model = new + model = new result model.new_record = false - model.from_rs result model end @@ -52,23 +51,23 @@ module Granite::Querying end # Returns the first row found that matches *criteria*. Otherwise `nil`. - def find_by(**criteria : Granite::Columns::Type) + def find_by(**criteria) find_by criteria.to_h end # :ditto: - def find_by(criteria : Granite::ModelArgs) + def find_by(criteria) clause, params = build_find_by_clause(criteria) first "WHERE #{clause}", params end # Returns the first row found that matches *criteria*. Otherwise raises a `NotFound` exception. - def find_by!(**criteria : Granite::Columns::Type) + def find_by!(**criteria) find_by!(criteria.to_h) end # :ditto: - def find_by!(criteria : Granite::ModelArgs) + def find_by!(criteria) find_by(criteria) || raise NotFound.new("No #{{{@type.name.stringify}}} found where #{criteria.map { |k, v| %(#{k} #{v.nil? ? "is NULL" : "= #{v}"}) }.join(" and ")}") end @@ -105,7 +104,7 @@ module Granite::Querying end # :ditto: - def exists?(criteria : Granite::ModelArgs) : Bool + def exists?(criteria) : Bool exec_exists *build_find_by_clause(criteria) end @@ -130,7 +129,7 @@ module Granite::Querying adapter.exists? quoted_table_name, clause, params end - private def build_find_by_clause(criteria : Granite::ModelArgs) + private def build_find_by_clause(criteria) keys = criteria.keys criteria_hash = criteria.dup diff --git a/src/granite/transactions.cr b/src/granite/transactions.cr index cec722de..d9384f65 100644 --- a/src/granite/transactions.cr +++ b/src/granite/transactions.cr @@ -6,23 +6,22 @@ module Granite::Transactions adapter.clear table_name end - def create(**args) - create(args.to_h) - end + # def create(**args) + # create(args.to_h) + # end - def create(args : Granite::ModelArgs) - instance = new - instance.set_attributes(args.transform_keys(&.to_s)) + def create(*args, **named_args) + instance = new(*args, **named_args) instance.save instance end - def create!(**args) - create!(args.to_h) + def create!(*args, **named_args) + create! *args, **named_args end - def create!(args : Granite::ModelArgs) - instance = create(args) + def create!(*args, **named_args) + instance = create(*args, **named_args) if instance.errors.any? raise Granite::RecordNotSaved.new(self.name, instance) @@ -170,15 +169,11 @@ module Granite::Transactions # will call the update method, otherwise it will call the create method. # This will update the timestamps appropriately. def save - {% begin %} - {% primary_key = @type.instance_vars.find { |ivar| (ann = ivar.annotation(Granite::Column)) && ann[:primary] } %} - {% raise raise "A primary key must be defined for #{@type.name}." unless primary_key %} - {% ann = primary_key.annotation(Granite::Column) %} return false unless valid? begin __before_save - if @{{primary_key.name.id}} && !new_record? + if primary_key_value && !new_record? __before_update __update __after_update @@ -196,7 +191,6 @@ module Granite::Transactions return false end true - {% end %} end def save! @@ -207,7 +201,7 @@ module Granite::Transactions update(args.to_h) end - def update(args : Granite::ModelArgs) + def update(args) set_attributes(args.transform_keys(&.to_s)) save @@ -217,7 +211,7 @@ module Granite::Transactions update!(args.to_h) end - def update!(args : Granite::ModelArgs) + def update!(args) set_attributes(args.transform_keys(&.to_s)) save! diff --git a/src/granite/type.cr b/src/granite/type.cr index 9bf1e197..ee8ba2df 100644 --- a/src/granite/type.cr +++ b/src/granite/type.cr @@ -17,20 +17,6 @@ module Granite::Type String => ".read", } - # :nodoc: - NUMERIC_TYPES = { - Int8 => ".to_i8", - Int16 => ".to_i16", - Int32 => ".to_i", - Int64 => ".to_i64", - UInt8 => ".to_u8", - UInt16 => ".to_u16", - UInt32 => ".to_u32", - UInt64 => ".to_u64", - Float32 => ".to_f32", - Float64 => ".to_f", - } - {% for type, method in PRIMITIVES %} # Converts a `DB::ResultSet` to `{{type}}`. def from_rs(result : DB::ResultSet, t : {{type}}.class) : {{type}} @@ -62,24 +48,4 @@ module Granite::Type def from_rs(result : DB::ResultSet, t : Time?.class) : Time? result.read(Time?).try &.in(Granite.settings.default_timezone) end - - {% for type, method in NUMERIC_TYPES %} - # Converts a `String` to `{{type}}`. - def convert_type(value : String, t : {{type.id}}.class) : {{type.id}} - value{{method.id}} - end - - # Converts a `String` to `{{type}}?`. - def convert_type(value : String, t : {{type.id}}?.class) : {{type.id}}? - value{{method.id}} - end - {% end %} - - def convert_type(value, type) - value - end - - def convert_type(value, type : Bool?.class) : Bool - ["1", "yes", "true", true, 1].includes?(value) - end end From b4ea9765eb5eacfd34ffe5da4a609db6b66a23a7 Mon Sep 17 00:00:00 2001 From: Blacksmoke16 Date: Sat, 5 Oct 2019 16:04:54 -0400 Subject: [PATCH 2/6] Simplify initialize method Use column_names when initializing from result_set Use property! for not nilable columns Add converter for bool type (for Sqlite) --- spec/granite/querying/query_builder_spec.cr | 6 ++--- src/granite/base.cr | 29 +++++++++++---------- src/granite/columns.cr | 9 +++---- src/granite/type.cr | 16 ++++++++++++ 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/spec/granite/querying/query_builder_spec.cr b/spec/granite/querying/query_builder_spec.cr index 647485cb..146f5f66 100644 --- a/spec/granite/querying/query_builder_spec.cr +++ b/spec/granite/querying/query_builder_spec.cr @@ -26,10 +26,10 @@ describe Granite::Query::BuilderMethods do {% if env("CURRENT_ADAPTER") == "sqlite" %} it "correctly queries bool fields" do Review.clear - Review.create(name: "one", published: true) - review2 = Review.create(name: "two", published: false) + Review.create(name: "one", published: 1) + review2 = Review.create(name: "two", published: 0) - found = Review.where(published: [false]).select + found = Review.where(published: [0]).select found.size.should eq 1 found[0].id.should eq review2.id diff --git a/src/granite/base.cr b/src/granite/base.cr index 4af30dfd..e0a1c0fc 100644 --- a/src/granite/base.cr +++ b/src/granite/base.cr @@ -61,10 +61,14 @@ abstract class Granite::Base disable_granite_docs? def initialize(result : DB::ResultSet) : Nil {% verbatim do %} {% begin %} - {% for column in @type.instance_vars.select { |ivar| ivar.annotation(Granite::Column) } %} - {% ann = column.annotation(Granite::Column) %} - @{{column.id}} = {% if ann[:converter] %} {{ann[:converter]}}.from_rs result {% else %} Granite::Type.from_rs(result, {{column.type}}) {% end %} - {% end %} + result.column_names.each do |col| + case col + {% for column in @type.instance_vars.select { |ivar| ivar.annotation(Granite::Column) } %} + {% ann = column.annotation(Granite::Column) %} + when {{column.name.stringify}} then @{{column.id}} = {% if ann[:converter] %} {{ann[:converter]}}.from_rs result {% else %} Granite::Type.from_rs(result, {{column.type}}) {% end %} + {% end %} + end + end {% end %} {% end %} end @@ -73,16 +77,13 @@ abstract class Granite::Base {% verbatim do %} {% begin %} {% for column, idx in @type.instance_vars.select { |ivar| (ann = ivar.annotation(Granite::Column)) && (!ann[:primary] || (ann[:primary] && ann[:auto] == false)) } %} - @{{column.id}} = if (val = args[{{idx}}]?) || (val = named_args[{{column.name.stringify}}]?) - val - else - {% if column.has_default_value? %} - {{column.default_value}} - {% elsif !column.type.nilable? %} - raise "Missing required property {{column}}" - {% else %} - nil - {% end %} + %args_val{idx} = args[{{idx}}]? + %named_args_val{idx} = named_args[{{column.name.stringify}}]? + + if !%args_val{idx}.nil? + @{{column.id}} = Granite::Type.convert_type %args_val{idx}, {{column.type}} + elsif !%named_args_val{idx}.nil? + @{{column.id}} = Granite::Type.convert_type %named_args_val{idx}, {{column.type}} end {% end %} {% end %} diff --git a/src/granite/columns.cr b/src/granite/columns.cr index 9c422f3d..8aa89d58 100644 --- a/src/granite/columns.cr +++ b/src/granite/columns.cr @@ -35,26 +35,23 @@ module Granite::Columns # Defines a column *decl* with the given *options*. macro column(decl, **options) {% type = decl.type %} - # Raise an exception if the delc type has more than 2 union types or if it has 2 types without nil - # This prevents having a column typed to String | Int32 etc. {% if type.is_a?(Union) && (type.types.size > 2 || (type.types.size == 2 && !type.types.any?(&.resolve.nilable?))) %} {% raise "The column #{@type.name}##{decl.var} cannot consist of a Union with a type other than `Nil`." %} {% end %} + {% nilable = type.resolve.nilable? %} {% column_type = (options[:column_type] && !options[:column_type].nil?) ? options[:column_type] : nil %} {% converter = (options[:converter] && !options[:converter].nil?) ? options[:converter] : nil %} {% primary = (options[:primary] && !options[:primary].nil?) ? options[:primary] : false %} {% auto = (options[:auto] && !options[:auto].nil?) ? options[:auto] : false %} {% auto = (!options || (options && options[:auto] == nil)) && primary %} - {% nilable = (type.is_a?(Path) ? type.resolve.nilable? : (type.is_a?(Union) ? type.types.any?(&.resolve.nilable?) : (type.is_a?(Generic) ? type.resolve.nilable? : type.nilable?))) %} - - {% if primary && !type.resolve.nilable? %} + {% if primary && !nilable %} {% raise "Primary key of #{@type} must be nilable" %} {% end %} @[Granite::Column(column_type: {{column_type}}, converter: {{converter}}, auto: {{auto}}, primary: {{primary}}, nilable: {{nilable}})] - {% if !primary || (primary && !auto) %} property {% else %} getter {% end %} {{decl.var}} : {{decl.type}} {% unless decl.value.is_a? Nop %} = {{decl.value}} {% end %} + {% if !primary || (primary && !auto) %} property{{(nilable || !decl.value.is_a?(Nop) ? "" : '!').id}} {% else %} getter {% end %} {{decl.var}} : {{decl.type}} {% unless decl.value.is_a? Nop %} = {{decl.value}} {% end %} end # include created_at and updated_at that will automatically be updated diff --git a/src/granite/type.cr b/src/granite/type.cr index ee8ba2df..10f7fc5a 100644 --- a/src/granite/type.cr +++ b/src/granite/type.cr @@ -48,4 +48,20 @@ module Granite::Type def from_rs(result : DB::ResultSet, t : Time?.class) : Time? result.read(Time?).try &.in(Granite.settings.default_timezone) end + + def convert_type(value, type) + value + end + + # Allow converting from interger based boolean to a `Bool` instance; such as for Sqlite. + def convert_type(value, type : Bool.class) : Bool + value == true || value == 1 + end + + # :ditto: + def convert_type(value, type : Bool?.class) : Bool? + return nil if value.nil? + + value == true || value == 1 + end end From d3bf8eee67f4350bed4a4be621415a357e33d141 Mon Sep 17 00:00:00 2001 From: Blacksmoke16 Date: Mon, 7 Oct 2019 21:10:02 -0400 Subject: [PATCH 3/6] Implement update functionality Simplify initialization again --- spec/granite/querying/query_builder_spec.cr | 4 +-- spec/granite/transactions/update_spec.cr | 10 ++++++ spec/granite_spec.cr | 27 -------------- src/granite/base.cr | 39 +++++++++------------ src/granite/query/executors/list.cr | 2 +- src/granite/transactions.cr | 30 +++------------- 6 files changed, 34 insertions(+), 78 deletions(-) diff --git a/spec/granite/querying/query_builder_spec.cr b/spec/granite/querying/query_builder_spec.cr index 146f5f66..7c03dc54 100644 --- a/spec/granite/querying/query_builder_spec.cr +++ b/spec/granite/querying/query_builder_spec.cr @@ -26,8 +26,8 @@ describe Granite::Query::BuilderMethods do {% if env("CURRENT_ADAPTER") == "sqlite" %} it "correctly queries bool fields" do Review.clear - Review.create(name: "one", published: 1) - review2 = Review.create(name: "two", published: 0) + Review.create(name: "one", published: true) + review2 = Review.create(name: "two", published: false) found = Review.where(published: [0]).select diff --git a/spec/granite/transactions/update_spec.cr b/spec/granite/transactions/update_spec.cr index e2577a30..77b33a43 100644 --- a/spec/granite/transactions/update_spec.cr +++ b/spec/granite/transactions/update_spec.cr @@ -11,6 +11,16 @@ pending "#update" do Parent.find!(parent.id).name.should eq "Other parent" end + it "allows setting a value to nil" do + model = Teacher.create!(name: "New Parent") + + model.update(name: nil) + + model.name.should be_nil + + Teacher.find!(model.id).name.should be_nil + end + it "does not update an invalid object" do parent = Parent.new(name: "New Parent") parent.save! diff --git a/spec/granite_spec.cr b/spec/granite_spec.cr index 4a397297..02879ea8 100644 --- a/spec/granite_spec.cr +++ b/spec/granite_spec.cr @@ -39,15 +39,6 @@ describe Granite::Base do end end - pending "with a hash" do - it "should instaniate correctly" do - model = DefaultValues.new({"name" => "Bob", "age" => 3.14}) - 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 @@ -58,24 +49,6 @@ describe Granite::Base do model.field_uuid.should eq uuid end end - - pending "with string numeric values" do - it "should instaniate correctly" do - model = StringConversion.new({"user_id" => "1", "int32" => "17", "float32" => "3.14", "float" => "92342.2342342"}) - - model.user_id.should be_a Int64 - model.user_id.should eq 1 - - model.int32.should be_a Int32 - model.int32.should eq 17 - - model.float32.should be_a Float32 - model.float32.should eq 3.14_f32 - - model.float.should be_a Float64 - model.float.should eq 92342.2342342 - end - end end describe Logger do diff --git a/src/granite/base.cr b/src/granite/base.cr index e0a1c0fc..2bf1262e 100644 --- a/src/granite/base.cr +++ b/src/granite/base.cr @@ -43,13 +43,9 @@ abstract class Granite::Base @@select = Container.new(table_name: table_name, fields: fields) # Returns true if this object hasn't been saved yet. - @[JSON::Field(ignore: true)] - @[YAML::Field(ignore: true)] disable_granite_docs? property? new_record : Bool = true # Returns true if this object has been destroyed. - @[JSON::Field(ignore: true)] - @[YAML::Field(ignore: true)] disable_granite_docs? getter? destroyed : Bool = false # Returns true if the record is persisted. @@ -57,35 +53,32 @@ abstract class Granite::Base !(new_record? || destroyed?) end - # Consumes the result set to set self's property values. - disable_granite_docs? def initialize(result : DB::ResultSet) : Nil - {% verbatim do %} - {% begin %} - result.column_names.each do |col| - case col - {% for column in @type.instance_vars.select { |ivar| ivar.annotation(Granite::Column) } %} - {% ann = column.annotation(Granite::Column) %} - when {{column.name.stringify}} then @{{column.id}} = {% if ann[:converter] %} {{ann[:converter]}}.from_rs result {% else %} Granite::Type.from_rs(result, {{column.type}}) {% end %} - {% end %} + # Consumes the result set to set self's property values. + disable_granite_docs? def initialize(result : DB::ResultSet) : Nil + {% verbatim do %} + {% begin %} + result.column_names.each do |col| + case col + {% for column in @type.instance_vars.select { |ivar| ivar.annotation(Granite::Column) } %} + {% ann = column.annotation(Granite::Column) %} + when {{column.name.stringify}} then @{{column.id}} = {% if ann[:converter] %} {{ann[:converter]}}.from_rs result {% else %} Granite::Type.from_rs(result, {{column.type}}) {% end %} + {% end %} + end end - end + {% end %} {% end %} - {% end %} - end + end disable_granite_docs? def initialize(*args, **named_args) {% verbatim do %} {% begin %} {% for column, idx in @type.instance_vars.select { |ivar| (ann = ivar.annotation(Granite::Column)) && (!ann[:primary] || (ann[:primary] && ann[:auto] == false)) } %} %args_val{idx} = args[{{idx}}]? - %named_args_val{idx} = named_args[{{column.name.stringify}}]? + %named_args_val{idx} = named_args[{{column.name.stringify}}]? || named_args[{{column.name.symbolize}}]? - if !%args_val{idx}.nil? - @{{column.id}} = Granite::Type.convert_type %args_val{idx}, {{column.type}} - elsif !%named_args_val{idx}.nil? - @{{column.id}} = Granite::Type.convert_type %named_args_val{idx}, {{column.type}} - end + @{{column.id}} = %args_val{idx} || %named_args_val{idx} || {{column.has_default_value? ? column.default_value : nil}} {% end %} + {{debug}} {% end %} {% end %} end diff --git a/src/granite/query/executors/list.cr b/src/granite/query/executors/list.cr index 6fbb3e97..5ca3e11e 100644 --- a/src/granite/query/executors/list.cr +++ b/src/granite/query/executors/list.cr @@ -13,7 +13,7 @@ module Granite::Query::Executor Model.adapter.open do |db| db.query @sql, args: @args do |record_set| record_set.each do - results << Model.from_rs record_set + results << Model.new record_set end end end diff --git a/src/granite/transactions.cr b/src/granite/transactions.cr index d9384f65..c8e4ef66 100644 --- a/src/granite/transactions.cr +++ b/src/granite/transactions.cr @@ -6,26 +6,16 @@ module Granite::Transactions adapter.clear table_name end - # def create(**args) - # create(args.to_h) - # end - def create(*args, **named_args) instance = new(*args, **named_args) instance.save instance end - def create!(*args, **named_args) - create! *args, **named_args - end - def create!(*args, **named_args) instance = create(*args, **named_args) - if instance.errors.any? - raise Granite::RecordNotSaved.new(self.name, instance) - end + raise Granite::RecordNotSaved.new(self.name, instance) if instance.errors.any? instance end @@ -197,23 +187,13 @@ module Granite::Transactions save || raise Granite::RecordNotSaved.new(self.class.name, self) end - def update(**args) - update(args.to_h) - end - - def update(args) - set_attributes(args.transform_keys(&.to_s)) - + def update(*args, **named_args) + initialize(*args, **named_args) save end - def update!(**args) - update!(args.to_h) - end - - def update!(args) - set_attributes(args.transform_keys(&.to_s)) - + def update!(*args, **named_args) + initialize(*args, **named_args) save! end From 104df0b64083febe9d61bf603d136c461e6efcd3 Mon Sep 17 00:00:00 2001 From: Blacksmoke16 Date: Mon, 7 Oct 2019 21:16:05 -0400 Subject: [PATCH 4/6] Some cleanup --- src/granite/base.cr | 1 - src/granite/type.cr | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/src/granite/base.cr b/src/granite/base.cr index 2bf1262e..83cd7422 100644 --- a/src/granite/base.cr +++ b/src/granite/base.cr @@ -78,7 +78,6 @@ abstract class Granite::Base @{{column.id}} = %args_val{idx} || %named_args_val{idx} || {{column.has_default_value? ? column.default_value : nil}} {% end %} - {{debug}} {% end %} {% end %} end diff --git a/src/granite/type.cr b/src/granite/type.cr index 10f7fc5a..ee8ba2df 100644 --- a/src/granite/type.cr +++ b/src/granite/type.cr @@ -48,20 +48,4 @@ module Granite::Type def from_rs(result : DB::ResultSet, t : Time?.class) : Time? result.read(Time?).try &.in(Granite.settings.default_timezone) end - - def convert_type(value, type) - value - end - - # Allow converting from interger based boolean to a `Bool` instance; such as for Sqlite. - def convert_type(value, type : Bool.class) : Bool - value == true || value == 1 - end - - # :ditto: - def convert_type(value, type : Bool?.class) : Bool? - return nil if value.nil? - - value == true || value == 1 - end end From c882c38c5f10c964e4ade4a6888292bf27987387 Mon Sep 17 00:00:00 2001 From: Blacksmoke16 Date: Sun, 13 Oct 2019 19:55:59 -0400 Subject: [PATCH 5/6] Remove default initializer --- spec/granite/columns/primary_key_spec.cr | 4 -- spec/granite/transactions/save_spec.cr | 17 ----- spec/granite/transactions/touch_spec.cr | 2 +- spec/granite/transactions/update_spec.cr | 77 ----------------------- spec/spec_models.cr | 58 +++++++++++++++-- src/granite/base.cr | 13 +--- src/granite/transactions.cr | 80 ++++++++++-------------- 7 files changed, 89 insertions(+), 162 deletions(-) delete mode 100644 spec/granite/transactions/update_spec.cr diff --git a/spec/granite/columns/primary_key_spec.cr b/spec/granite/columns/primary_key_spec.cr index cc92d816..7817a2a6 100644 --- a/spec/granite/columns/primary_key_spec.cr +++ b/spec/granite/columns/primary_key_spec.cr @@ -11,10 +11,6 @@ describe "#new" do end describe "#new(primary_key: value)" do - it "ignores the value in default" do - Parent.new(id: 1_i64).id.should eq(nil) - end - it "sets the value when the primary is defined as `auto: false`" do Kvs.new(k: "foo").k.should eq("foo") Kvs.new(k: "foo", v: "v").k.should eq("foo") diff --git a/spec/granite/transactions/save_spec.cr b/spec/granite/transactions/save_spec.cr index c08d56bd..47de19b1 100644 --- a/spec/granite/transactions/save_spec.cr +++ b/spec/granite/transactions/save_spec.cr @@ -15,12 +15,6 @@ describe "#save" do parent.persisted?.should be_false end - pending "does not save a model with type conversion errors" do - model = Comment.new(articleid: "foo") - model.errors.size.should eq 1 - model.save.should be_false - end - it "updates an existing object" do Parent.clear parent = Parent.new @@ -46,17 +40,6 @@ describe "#save" do parent.name.should eq "Test Parent" end - pending "does not update when the conflicted primary key is given to the new record" do - parent1 = Parent.new - parent1.name = "Test Parent" - parent1.save.should be_true - - parent2 = Parent.new - parent2.id = parent1.id - parent2.name = "Test Parent2" - parent2.save.should be_false - end - describe "with a custom primary key" do it "creates a new object" do school = School.new diff --git a/spec/granite/transactions/touch_spec.cr b/spec/granite/transactions/touch_spec.cr index 0251dad9..5d59a7de 100644 --- a/spec/granite/transactions/touch_spec.cr +++ b/spec/granite/transactions/touch_spec.cr @@ -6,7 +6,7 @@ describe "#touch" do end it "should raise on non existent field" do - expect_raises Exception, "Field 'foo' does not exist on type 'TimeTest'." do + expect_raises Exception, "Column 'foo' does not exist on type 'TimeTest'." do model = TimeTest.create(name: "foo") model.touch(:foo) end diff --git a/spec/granite/transactions/update_spec.cr b/spec/granite/transactions/update_spec.cr deleted file mode 100644 index 77b33a43..00000000 --- a/spec/granite/transactions/update_spec.cr +++ /dev/null @@ -1,77 +0,0 @@ -require "../../spec_helper" - -pending "#update" do - it "updates an object" do - parent = Parent.new(name: "New Parent") - parent.save! - - parent.update(name: "Other parent").should be_true - parent.name.should eq "Other parent" - - Parent.find!(parent.id).name.should eq "Other parent" - end - - it "allows setting a value to nil" do - model = Teacher.create!(name: "New Parent") - - model.update(name: nil) - - model.name.should be_nil - - Teacher.find!(model.id).name.should be_nil - end - - it "does not update an invalid object" do - parent = Parent.new(name: "New Parent") - parent.save! - - parent.update(name: "").should be_false - parent.name.should eq "" - - Parent.find!(parent.id).name.should eq "New Parent" - end - - context "when created_at is nil" do - it "does not update created_at" do - parent = Parent.new(name: "New Parent") - parent.save! - - created_at = parent.created_at!.at_beginning_of_second - - # Simulating instantiating a new object with same ID - new_parent = Parent.new(name: "New New Parent") - new_parent.id = parent.id - new_parent.new_record = false - new_parent.updated_at = parent.updated_at - new_parent.save! - - saved_parent = Parent.find!(parent.id) - saved_parent.name.should eq "New New Parent" - saved_parent.created_at.should eq created_at - saved_parent.updated_at.should eq Time.utc.at_beginning_of_second - end - end -end - -pending "#update!" do - it "updates an object" do - parent = Parent.new(name: "New Parent") - parent.save! - - parent.update!(name: "Other parent") - parent.name.should eq "Other parent" - - Parent.find!(parent.id).name.should eq "Other parent" - end - - it "does not update but raises an exception" do - parent = Parent.new(name: "New Parent") - parent.save! - - expect_raises(Granite::RecordNotSaved, "Parent") do - parent.update!(name: "") - end - - Parent.find!(parent.id).name.should eq "New Parent" - end -end diff --git a/spec/spec_models.cr b/spec/spec_models.cr index 10734f8c..d095ae8d 100644 --- a/spec/spec_models.cr +++ b/spec/spec_models.cr @@ -12,6 +12,8 @@ end connection {{ adapter_literal }} table parents + def initialize(@name : String?); end + column id : Int64?, primary: true column name : String? timestamps @@ -37,6 +39,8 @@ end connection {{ adapter_literal }} table students + def initialize(@name : String?); end + column id : Int64?, primary: true column name : String? @@ -48,6 +52,8 @@ end connection {{ adapter_literal }} table klasses + def initialize(@name : String?); end + column id : Int64?, primary: true column name : String? @@ -71,6 +77,8 @@ end connection {{ adapter_literal }} table schools + def initialize(@name : String?); end + column custom_id : Int64?, primary: true column name : String? end @@ -128,6 +136,8 @@ end connection {{ adapter_literal }} table nation_counties + def initialize(@name : String?); end + column id : Int64?, primary: true column name : String? end @@ -136,6 +146,16 @@ end connection {{ adapter_literal }} table reviews + def initialize( + @name : String? = nil, + @downvotes : Int32? = nil, + @upvotes : Int64? = nil, + @sentiment : Float32? = nil, + @interest : Float64? = nil, + @published : Bool? = nil + ) + end + column id : Int64?, primary: true column name : String? column downvotes : Int32? @@ -157,6 +177,8 @@ end connection {{ adapter_literal }} table "select" + def initialize(@all : String?); end + column id : Int64?, primary: true column all : String? end @@ -165,6 +187,8 @@ end connection {{ adapter_literal }} table callbacks + def initialize(@name : String?); end + column id : Int64?, primary: true column name : String? @@ -182,6 +206,8 @@ end connection {{ adapter_literal }} table callbacks_with_abort + def initialize(@name : String? = nil, @abort_at : String? = nil, @do_abort : Bool? = nil); end + column abort_at : String?, primary: true, auto: false column do_abort : Bool? column name : String? @@ -200,6 +226,8 @@ end connection {{ adapter_literal }} table kvs + def initialize(@k : String, @v : String? = nil); end + column k : String?, primary: true, auto: false column v : String? end @@ -224,11 +252,11 @@ end connection {{ adapter_literal }} table books + def initialize(@name : String?); end + column id : Int32?, primary: true column name : String? - @[JSON::Field(ignore: true)] - @[YAML::Field(ignore: true)] belongs_to publisher : Company, foreign_key: publisher_id : Int32? has_many :book_reviews, class_name: BookReview belongs_to author : Person @@ -248,6 +276,8 @@ end connection {{ adapter_literal }} table items + def initialize(@item_name : String?, @item_id : String? = nil); end + column item_id : String?, primary: true, auto: false column item_name : String? @@ -262,6 +292,8 @@ end connection {{ adapter_literal }} table non_auto_default_pk + def initialize(@name : String? = nil, @id : Int64? = nil); end + column id : Int64?, primary: true, auto: false column name : String? end @@ -270,6 +302,8 @@ end connection {{ adapter_literal }} table non_auto_custom_pk + def initialize(@name : String? = nil, @custom_id : Int64? = nil); end + column custom_id : Int64?, primary: true, auto: false column name : String? end @@ -286,6 +320,8 @@ end connection {{ adapter_literal }} table comments + def initialize(@commentbody : String? = nil, @articleid : Int64? = nil); end + column id : Int64?, primary: true column commentbody : String? column articleid : Int64? @@ -322,6 +358,8 @@ end connection {{ adapter_literal }} table todos + def initialize(@name : String?, @priority : Int32?); end + column id : Int64?, primary: true column name : String? column priority : Int32? @@ -381,6 +419,8 @@ end connection {{ adapter_literal }} table uuids + def initialize(@uuid : UUID?, @field_uuid : UUID?); end + column uuid : UUID?, primary: true, converter: Granite::Converters::Uuid(String), auto: false column field_uuid : UUID?, converter: Granite::Converters::Uuid(String) end @@ -427,16 +467,20 @@ end connection {{ adapter_literal }} table defaults + def initialize(@name : String = "Jim", @is_alive : Bool = true, @age : Float64 = 0.0); end + column id : Int64?, primary: true - column name : String = "Jim" - column is_alive : Bool = true - column age : Float64 = 0.0 + column name : String + column is_alive : Bool + column age : Float64 end class TimeTest < Granite::Base connection {{ adapter_literal }} table times + def initialize(@test : Time? = nil, @name : String? = nil); end + column id : Int64?, primary: true column test : Time? column name : String? @@ -455,6 +499,8 @@ end connection {{ adapter_literal }} table "event_cons" + def initialize(@con_name : String, @event_name : String?); end + column id : Int64?, primary: true column con_name : String column event_name : String? @@ -537,6 +583,8 @@ end connection {{ adapter_literal }} table converters + def initialize(@binary_uuid : UUID?, @string_uuid : UUID?); end + column id : Int64?, primary: true column binary_json : MyType?, column_type: "BLOB", converter: Granite::Converters::Json(MyType, Bytes) diff --git a/src/granite/base.cr b/src/granite/base.cr index 83cd7422..bbd50efc 100644 --- a/src/granite/base.cr +++ b/src/granite/base.cr @@ -69,17 +69,6 @@ abstract class Granite::Base {% end %} end - disable_granite_docs? def initialize(*args, **named_args) - {% verbatim do %} - {% begin %} - {% for column, idx in @type.instance_vars.select { |ivar| (ann = ivar.annotation(Granite::Column)) && (!ann[:primary] || (ann[:primary] && ann[:auto] == false)) } %} - %args_val{idx} = args[{{idx}}]? - %named_args_val{idx} = named_args[{{column.name.stringify}}]? || named_args[{{column.name.symbolize}}]? - - @{{column.id}} = %args_val{idx} || %named_args_val{idx} || {{column.has_default_value? ? column.default_value : nil}} - {% end %} - {% end %} - {% end %} - end + def initialize; end end end diff --git a/src/granite/transactions.cr b/src/granite/transactions.cr index c8e4ef66..6d8d44f8 100644 --- a/src/granite/transactions.cr +++ b/src/granite/transactions.cr @@ -6,17 +6,15 @@ module Granite::Transactions adapter.clear table_name end - def create(*args, **named_args) + def create(*args, **named_args) : self instance = new(*args, **named_args) instance.save instance end - def create!(*args, **named_args) + def create!(*args, **named_args) : self instance = create(*args, **named_args) - - raise Granite::RecordNotSaved.new(self.name, instance) if instance.errors.any? - + instance.save! instance end @@ -124,35 +122,35 @@ module Granite::Transactions private def __update {% begin %} - {% primary_key = @type.instance_vars.find { |ivar| (ann = ivar.annotation(Granite::Column)) && ann[:primary] } %} - {% raise raise "A primary key must be defined for #{@type.name}." unless primary_key %} - {% ann = primary_key.annotation(Granite::Column) %} - set_timestamps mode: :update - fields = self.class.content_fields.dup - params = content_values + [@{{primary_key.name.id}}] - - # Do not update created_at on update - if created_at_index = fields.index("created_at") - fields.delete_at created_at_index - params.delete_at created_at_index - end + {% primary_key = @type.instance_vars.find { |ivar| (ann = ivar.annotation(Granite::Column)) && ann[:primary] } %} + {% raise raise "A primary key must be defined for #{@type.name}." unless primary_key %} + {% ann = primary_key.annotation(Granite::Column) %} + set_timestamps mode: :update + fields = self.class.content_fields.dup + params = content_values + [@{{primary_key.name.id}}] - begin - self.class.adapter.update(self.class.table_name, self.class.primary_name, fields, params) - rescue err - raise DB::Error.new(err.message) - end - {% end %} + # Do not update created_at on update + if created_at_index = fields.index("created_at") + fields.delete_at created_at_index + params.delete_at created_at_index + end + + begin + self.class.adapter.update(self.class.table_name, self.class.primary_name, fields, params) + rescue err + raise DB::Error.new(err.message) + end + {% end %} end private def __destroy {% begin %} - {% primary_key = @type.instance_vars.find { |ivar| (ann = ivar.annotation(Granite::Column)) && ann[:primary] } %} - {% raise raise "A primary key must be defined for #{@type.name}." unless primary_key %} - {% ann = primary_key.annotation(Granite::Column) %} - self.class.adapter.delete(self.class.table_name, self.class.primary_name, @{{primary_key.name.id}}) - @destroyed = true - {% end %} + {% primary_key = @type.instance_vars.find { |ivar| (ann = ivar.annotation(Granite::Column)) && ann[:primary] } %} + {% raise raise "A primary key must be defined for #{@type.name}." unless primary_key %} + {% ann = primary_key.annotation(Granite::Column) %} + self.class.adapter.delete(self.class.table_name, self.class.primary_name, @{{primary_key.name.id}}) + @destroyed = true + {% end %} end # The save method will check to see if the primary exists yet. If it does it @@ -187,16 +185,6 @@ module Granite::Transactions save || raise Granite::RecordNotSaved.new(self.class.name, self) end - def update(*args, **named_args) - initialize(*args, **named_args) - save - end - - def update!(*args, **named_args) - initialize(*args, **named_args) - save! - end - # Destroy will remove this from the database. def destroy begin @@ -217,20 +205,20 @@ module Granite::Transactions destroy || raise Granite::RecordNotDestroyed.new(self.class.name, self) end - # Saves the record with the *updated_at*/*names* fields updated to the current time. - def touch(*fields) : Bool + # Saves the record with the *updated_at*/*columns* updated to the current time. + def touch(*columns) : Bool raise "Cannot touch on a new record object" unless persisted? {% begin %} - fields.each do |field| - case field.to_s + columns.each do |column| + case column.to_s {% for time_field in @type.instance_vars.select { |ivar| ivar.type == Time? } %} when {{time_field.stringify}} then @{{time_field.id}} = Time.local(Granite.settings.default_timezone).at_beginning_of_second {% end %} else - if {{@type.instance_vars.map(&.name.stringify)}}.includes? field.to_s - raise "{{@type.name}}.#{field} cannot be touched. It is not of type `Time`." + if {{@type.instance_vars.map(&.name.stringify)}}.includes? column.to_s + raise "{{@type.name}}.#{column} cannot be touched. It is not of type `Time`." else - raise "Field '#{field}' does not exist on type '{{@type.name}}'." + raise "Column '#{column}' does not exist on type '{{@type.name}}'." end end end From bae4526dd3c17187c1f8cac470297809abab9af3 Mon Sep 17 00:00:00 2001 From: Blacksmoke16 Date: Sat, 26 Oct 2019 12:49:35 -0400 Subject: [PATCH 6/6] Use getter! for PK Require PK type be not nilable Add specs to make sure internal ivars are not included in .to_json/yaml --- spec/adapter/adapters_spec.cr | 4 +- spec/granite/callbacks/callbacks_spec.cr | 4 +- spec/granite/columns/uuid_spec.cr | 2 +- spec/granite/select/select_spec.cr | 2 +- .../validation_helpers/uniqueness_spec.cr | 10 +- spec/granite/validations/validator_spec.cr | 6 +- spec/granite_spec.cr | 12 ++ spec/spec_models.cr | 113 +++++++++--------- src/granite/base.cr | 4 + src/granite/columns.cr | 16 +-- src/granite/validation_helpers/uniqueness.cr | 2 +- 11 files changed, 98 insertions(+), 77 deletions(-) diff --git a/spec/adapter/adapters_spec.cr b/spec/adapter/adapters_spec.cr index eeba3262..3b4cc5c2 100644 --- a/spec/adapter/adapters_spec.cr +++ b/spec/adapter/adapters_spec.cr @@ -3,11 +3,11 @@ require "../spec_helper" class Foo < Granite::Base connection sqlite - column id : Int64?, primary: true + column id : Int64, primary: true end class Bar < Granite::Base - column id : Int64?, primary: true + column id : Int64, primary: true end describe Granite::Connections do diff --git a/spec/granite/callbacks/callbacks_spec.cr b/spec/granite/callbacks/callbacks_spec.cr index 89e91f27..c52dcd12 100644 --- a/spec/granite/callbacks/callbacks_spec.cr +++ b/spec/granite/callbacks/callbacks_spec.cr @@ -59,7 +59,7 @@ describe "(callback feature)" do context "on a single model" do it "should successfully trigger the callback" do item = Item.new(item_name: "item1") - item.item_id.should be_nil + item.item_id?.should be_nil item.before_create @@ -75,7 +75,7 @@ describe "(callback feature)" do items << Item.new(item_name: "item3") items << Item.new(item_name: "item4") - items.all? { |item| item.item_id.nil? }.should be_true + items.all? { |item| item.item_id?.nil? }.should be_true items.each(&.before_create) diff --git a/spec/granite/columns/uuid_spec.cr b/spec/granite/columns/uuid_spec.cr index 3c2067af..230229c7 100644 --- a/spec/granite/columns/uuid_spec.cr +++ b/spec/granite/columns/uuid_spec.cr @@ -3,7 +3,7 @@ require "../../spec_helper" describe "UUID creation" do it "correctly sets a RFC4122 V4 UUID on save" do item = UUIDModel.new - item.uuid.should be_nil + item.uuid?.should be_nil item.save item.uuid.should be_a(UUID) item.uuid.try(&.version.v4?).should be_true diff --git a/spec/granite/select/select_spec.cr b/spec/granite/select/select_spec.cr index cf5a8c2c..392046a5 100644 --- a/spec/granite/select/select_spec.cr +++ b/spec/granite/select/select_spec.cr @@ -28,7 +28,7 @@ describe "custom select" do EventCon.create(con_name: "Con2", event_name: "Event2") EventCon.all.each_with_index do |env, idx| - env.id.should be_nil + env.id?.should be_nil env.con_name.should eq "Con#{idx}" env.event_name.should be_nil end diff --git a/spec/granite/validation_helpers/uniqueness_spec.cr b/spec/granite/validation_helpers/uniqueness_spec.cr index c8fe1164..a80c8bfa 100644 --- a/spec/granite/validation_helpers/uniqueness_spec.cr +++ b/spec/granite/validation_helpers/uniqueness_spec.cr @@ -2,11 +2,9 @@ require "../../spec_helper" describe Granite::ValidationHelpers do context "Uniqueness" do - Spec.before_each do + it "should work for uniqueness" do Validators::PersonUniqueness.migrator.drop_and_create - end - it "should work for uniqueness" do person_uniqueness1 = Validators::PersonUniqueness.new person_uniqueness2 = Validators::PersonUniqueness.new @@ -16,19 +14,21 @@ describe Granite::ValidationHelpers do person_uniqueness1.save person_uniqueness2.save - person_uniqueness1.errors.size.should eq 0 + person_uniqueness1.errors.should be_empty person_uniqueness2.errors.size.should eq 1 person_uniqueness2.errors[0].message.should eq "name should be unique" end it "should work for uniqueness on the same instance" do + Validators::PersonUniqueness.migrator.drop_and_create + person_uniqueness1 = Validators::PersonUniqueness.new person_uniqueness1.name = "awesomeName" person_uniqueness1.save - person_uniqueness1.errors.size.should eq 0 + person_uniqueness1.errors.should be_empty person_uniqueness1.name = "awesomeName" person_uniqueness1.save diff --git a/spec/granite/validations/validator_spec.cr b/spec/granite/validations/validator_spec.cr index 354c71f5..acaefdc3 100644 --- a/spec/granite/validations/validator_spec.cr +++ b/spec/granite/validations/validator_spec.cr @@ -5,7 +5,7 @@ require "../../spec_helper" class NameTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? validate :name, "cannot be blank", ->(s : NameTest) do @@ -16,7 +16,7 @@ require "../../spec_helper" class EmailTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column email : String? validate :email, "cannot be blank" do |email_test| @@ -27,7 +27,7 @@ require "../../spec_helper" class PasswordTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column password : String? column password_validation : String? diff --git a/spec/granite_spec.cr b/spec/granite_spec.cr index 02879ea8..1a095a50 100644 --- a/spec/granite_spec.cr +++ b/spec/granite_spec.cr @@ -51,6 +51,18 @@ describe Granite::Base do end end + describe JSON do + it "should not include internal ivars" do + DefaultValues.new.to_json.should eq %({"name":"Jim","is_alive":true,"age":0.0}) + end + end + + describe YAML do + it "should not include internal ivars" do + DefaultValues.new.to_yaml.should eq %(---\nname: Jim\nis_alive: true\nage: 0.0\n) + end + end + describe Logger do describe "when logger is set to IO" do it "should be logged as DEBUG" do diff --git a/spec/spec_models.cr b/spec/spec_models.cr index d095ae8d..d753929a 100644 --- a/spec/spec_models.cr +++ b/spec/spec_models.cr @@ -14,7 +14,7 @@ end def initialize(@name : String?); end - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? timestamps @@ -29,7 +29,7 @@ end connection {{ adapter_literal }} table teachers - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? has_many :klasses, class_name: Klass @@ -41,7 +41,7 @@ end def initialize(@name : String?); end - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? has_many :enrollments, class_name: Enrollment @@ -54,7 +54,7 @@ end def initialize(@name : String?); end - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? belongs_to teacher : Teacher @@ -67,7 +67,7 @@ end connection {{ adapter_literal }} table enrollments - column id : Int64?, primary: true + column id : Int64, primary: true belongs_to :student belongs_to :klass @@ -79,7 +79,7 @@ end def initialize(@name : String?); end - column custom_id : Int64?, primary: true + column custom_id : Int64, primary: true column name : String? end @@ -87,7 +87,7 @@ end connection {{ adapter_literal }} table users - column id : Int64?, primary: true + column id : Int64, primary: true column email : String? has_one :profile @@ -97,7 +97,7 @@ end connection {{ adapter_literal }} table characters - column character_id : Int32?, primary: true, auto: false + column character_id : Int32, primary: true, auto: false column name : String end @@ -105,7 +105,7 @@ end connection {{ adapter_literal }} table couriers - column courier_id : Int32?, primary: true, auto: false + column courier_id : Int32, primary: true, auto: false column issuer_id : Int32 belongs_to service : CourierService, primary_key: "owner_id" @@ -116,7 +116,7 @@ end connection {{ adapter_literal }} table services - column owner_id : Int64?, primary: true, auto: false + column owner_id : Int64, primary: true, auto: false column name : String has_many :couriers, class_name: Courier, foreign_key: "service_id" @@ -126,7 +126,7 @@ end connection {{ adapter_literal }} table profiles - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? belongs_to :user @@ -138,7 +138,7 @@ end def initialize(@name : String?); end - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? end @@ -156,7 +156,7 @@ end ) end - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? column downvotes : Int32? column upvotes : Int64? @@ -170,7 +170,7 @@ end connection {{ adapter_literal }} table empties - column id : Int64?, primary: true + column id : Int64, primary: true end class ReservedWord < Granite::Base @@ -179,7 +179,7 @@ end def initialize(@all : String?); end - column id : Int64?, primary: true + column id : Int64, primary: true column all : String? end @@ -189,7 +189,7 @@ end def initialize(@name : String?); end - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? property history : IO::Memory = IO::Memory.new @@ -208,7 +208,7 @@ end def initialize(@name : String? = nil, @abort_at : String? = nil, @do_abort : Bool? = nil); end - column abort_at : String?, primary: true, auto: false + column abort_at : String, primary: true, auto: false column do_abort : Bool? column name : String? @@ -228,7 +228,7 @@ end def initialize(@k : String, @v : String? = nil); end - column k : String?, primary: true, auto: false + column k : String, primary: true, auto: false column v : String? end @@ -236,7 +236,7 @@ end connection {{ adapter_literal }} table people - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? end @@ -244,7 +244,7 @@ end connection {{ adapter_literal }} table companies - column id : Int32?, primary: true + column id : Int32, primary: true column name : String? end @@ -254,7 +254,7 @@ end def initialize(@name : String?); end - column id : Int32?, primary: true + column id : Int32, primary: true column name : String? belongs_to publisher : Company, foreign_key: publisher_id : Int32? @@ -266,7 +266,7 @@ end connection {{ adapter_literal }} table book_reviews - column id : Int32?, primary: true + column id : Int32, primary: true column body : String? belongs_to book : Book, foreign_key: book_id : Int32? @@ -278,7 +278,7 @@ end def initialize(@item_name : String?, @item_id : String? = nil); end - column item_id : String?, primary: true, auto: false + column item_id : String, primary: true, auto: false column item_name : String? before_create :generate_uuid @@ -294,7 +294,7 @@ end def initialize(@name : String? = nil, @id : Int64? = nil); end - column id : Int64?, primary: true, auto: false + column id : Int64, primary: true, auto: false column name : String? end @@ -304,7 +304,7 @@ end def initialize(@name : String? = nil, @custom_id : Int64? = nil); end - column custom_id : Int64?, primary: true, auto: false + column custom_id : Int64, primary: true, auto: false column name : String? end @@ -312,7 +312,7 @@ end connection {{ adapter_literal }} table articles - column id : Int64?, primary: true + column id : Int64, primary: true column articlebody : String? end @@ -322,7 +322,7 @@ end def initialize(@commentbody : String? = nil, @articleid : Int64? = nil); end - column id : Int64?, primary: true + column id : Int64, primary: true column commentbody : String? column articleid : Int64? end @@ -330,7 +330,7 @@ end class SongThread < Granite::Base connection {{ env("CURRENT_ADAPTER").id }} - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? end @@ -338,7 +338,7 @@ end connection {{ env("CURRENT_ADAPTER").id }} table custom_table_name - column custom_primary_key : Int64?, primary: true + column custom_primary_key : Int64, primary: true column name : String? end @@ -348,7 +348,7 @@ end connection {{ adapter_literal }} table todos - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? column priority : Int32? timestamps @@ -360,7 +360,7 @@ end def initialize(@name : String?, @priority : Int32?); end - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? column priority : Int32? timestamps @@ -370,7 +370,7 @@ end connection {{ adapter_literal }} table after_json_init - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? column priority : Int32? @@ -382,7 +382,7 @@ end class ArticleViewModel < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column articlebody : String? column commentbody : String? @@ -396,7 +396,7 @@ end class ArrayModel < Granite::Base connection {{ adapter_literal }} - column id : Int32?, primary: true + column id : Int32, primary: true column str_array : Array(String)? column i16_array : Array(Int16)? column i32_array : Array(Int32)? @@ -412,7 +412,7 @@ end connection {{ adapter_literal }} table uuids - column uuid : UUID?, primary: true, converter: Granite::Converters::Uuid(String) + column uuid : UUID, primary: true, converter: Granite::Converters::Uuid(String) end class UUIDNaturalModel < Granite::Base @@ -421,7 +421,7 @@ end def initialize(@uuid : UUID?, @field_uuid : UUID?); end - column uuid : UUID?, primary: true, converter: Granite::Converters::Uuid(String), auto: false + column uuid : UUID, primary: true, converter: Granite::Converters::Uuid(String), auto: false column field_uuid : UUID?, converter: Granite::Converters::Uuid(String) end @@ -429,7 +429,7 @@ end connection {{ adapter_literal }} table todos_json - column id : Int64?, primary: true + column id : Int64, primary: true @[JSON::Field(key: "task_name")] column name : String? @@ -448,7 +448,7 @@ end connection {{ adapter_literal }} table todos_yaml - column id : Int64?, primary: true + column id : Int64, primary: true @[YAML::Field(key: "task_name")] column name : String? @@ -464,12 +464,15 @@ end end class DefaultValues < Granite::Base + include JSON::Serializable + include YAML::Serializable + connection {{ adapter_literal }} table defaults def initialize(@name : String = "Jim", @is_alive : Bool = true, @age : Float64 = 0.0); end - column id : Int64?, primary: true + column id : Int64, primary: true column name : String column is_alive : Bool column age : Float64 @@ -481,7 +484,7 @@ end def initialize(@test : Time? = nil, @name : String? = nil); end - column id : Int64?, primary: true + column id : Int64, primary: true column test : Time? column name : String? timestamps @@ -491,7 +494,7 @@ end connection {{ adapter_literal }} table manual_column_types - column id : Int64?, primary: true + column id : Int64, primary: true column foo : UUID?, column_type: "DECIMAL(12, 10)" end @@ -501,7 +504,7 @@ end def initialize(@con_name : String, @event_name : String?); end - column id : Int64?, primary: true + column id : Int64, primary: true column con_name : String column event_name : String? @@ -516,7 +519,7 @@ end belongs_to :user - column id : Int64?, primary: true + column id : Int64, primary: true column int32 : Int32 column float32 : Float32 column float : Float64 @@ -543,7 +546,7 @@ end connection {{ adapter_literal }} table enum_model - column id : Int64?, primary: true + column id : Int64, primary: true column my_enum : MyEnum?, column_type: "TEXT", converter: Granite::Converters::Enum(MyEnum, String) end @@ -552,7 +555,7 @@ end connection {{ adapter_literal }} table converters - column id : Int64?, primary: true + column id : Int64, primary: true column binary_json : MyType?, column_type: "BYTEA", converter: Granite::Converters::Json(MyType, Bytes) column string_json : MyType?, column_type: "JSON", converter: Granite::Converters::Json(MyType, JSON::Any) @@ -585,7 +588,7 @@ end def initialize(@binary_uuid : UUID?, @string_uuid : UUID?); end - column id : Int64?, primary: true + column id : Int64, primary: true column binary_json : MyType?, column_type: "BLOB", converter: Granite::Converters::Json(MyType, Bytes) column string_json : MyType?, column_type: "TEXT", converter: Granite::Converters::Json(MyType, String) @@ -602,7 +605,7 @@ end connection {{ adapter_literal }} table converters - column id : Int64?, primary: true + column id : Int64, primary: true column binary_json : MyType?, column_type: "BLOB", converter: Granite::Converters::Json(MyType, Bytes) column string_json : MyType?, column_type: "TEXT", converter: Granite::Converters::Json(MyType, String) @@ -621,7 +624,7 @@ end class NilTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column first_name_not_nil : String? column last_name_not_nil : String? @@ -651,7 +654,7 @@ end class BlankTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column first_name_not_blank : String? column last_name_not_blank : String? @@ -669,7 +672,7 @@ end class ChoiceTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column number_symbol : Int32? column type_array_symbol : String? @@ -686,7 +689,7 @@ end class LessThanTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column int_32_lt : Int32? column float_32_lt : Float32? @@ -704,7 +707,7 @@ end class GreaterThanTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column int_32_lt : Int32? column float_32_lt : Float32? @@ -722,7 +725,7 @@ end class LengthTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column title : String? column description : String? @@ -733,7 +736,7 @@ end class PersonUniqueness < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? validate_uniqueness :name @@ -742,7 +745,7 @@ end class ExclusionTest < Granite::Base connection {{ adapter_literal }} - column id : Int64?, primary: true + column id : Int64, primary: true column name : String? validate_exclusion :name, ["test_name"] diff --git a/src/granite/base.cr b/src/granite/base.cr index bbd50efc..fc9c84f7 100644 --- a/src/granite/base.cr +++ b/src/granite/base.cr @@ -42,9 +42,13 @@ abstract class Granite::Base macro inherited @@select = Container.new(table_name: table_name, fields: fields) + @[JSON::Field(ignore: true)] + @[YAML::Field(ignore: true)] # Returns true if this object hasn't been saved yet. disable_granite_docs? property? new_record : Bool = true + @[JSON::Field(ignore: true)] + @[YAML::Field(ignore: true)] # Returns true if this object has been destroyed. disable_granite_docs? getter? destroyed : Bool = false diff --git a/src/granite/columns.cr b/src/granite/columns.cr index 8aa89d58..3f92d7ff 100644 --- a/src/granite/columns.cr +++ b/src/granite/columns.cr @@ -46,12 +46,12 @@ module Granite::Columns {% auto = (options[:auto] && !options[:auto].nil?) ? options[:auto] : false %} {% auto = (!options || (options && options[:auto] == nil)) && primary %} - {% if primary && !nilable %} - {% raise "Primary key of #{@type} must be nilable" %} + {% if primary && nilable %} + {% raise "Primary key of #{@type} must be not-nilable" %} {% end %} @[Granite::Column(column_type: {{column_type}}, converter: {{converter}}, auto: {{auto}}, primary: {{primary}}, nilable: {{nilable}})] - {% if !primary || (primary && !auto) %} property{{(nilable || !decl.value.is_a?(Nop) ? "" : '!').id}} {% else %} getter {% end %} {{decl.var}} : {{decl.type}} {% unless decl.value.is_a? Nop %} = {{decl.value}} {% end %} + {% if !primary || (primary && !auto) %} property{{(nilable || !decl.value.is_a?(Nop) ? "" : '!').id}} {% else %} getter! {% end %} {{decl.var}} : {{decl.type}} {% unless decl.value.is_a? Nop %} = {{decl.value}} {% end %} end # include created_at and updated_at that will automatically be updated @@ -64,12 +64,14 @@ module Granite::Columns fields = {{"Hash(String, Union(#{@type.instance_vars.select { |ivar| ivar.annotation(Granite::Column) }.map(&.type.id).splat})).new".id}} {% for column in @type.instance_vars.select { |ivar| ivar.annotation(Granite::Column) } %} + {% ann = column.annotation(Granite::Column) %} + {% if column.type.id == Time.id %} - fields["{{column.name}}"] = {{column.name.id}}.try(&.in(Granite.settings.default_timezone).to_s(Granite::DATETIME_FORMAT)) + fields["{{column}}"] = {{column.id}}.try(&.in(Granite.settings.default_timezone).to_s(Granite::DATETIME_FORMAT)) {% elsif column.type.id == Slice.id %} - fields["{{column.name}}"] = {{column.name.id}}.try(&.to_s("")) + fields["{{column}}"] = {{column.id}}.try(&.to_s("")) {% else %} - fields["{{column.name}}"] = {{column.name.id}} + fields["{{column}}"] = @{{column.id}} {% end %} {% end %} @@ -92,7 +94,7 @@ module Granite::Columns {% begin %} {% primary_key = @type.instance_vars.find { |ivar| (ann = ivar.annotation(Granite::Column)) && ann[:primary] } %} {% raise raise "A primary key must be defined for #{@type.name}." unless primary_key %} - {{primary_key.id}} + {{primary_key.id}}? {% end %} end end diff --git a/src/granite/validation_helpers/uniqueness.cr b/src/granite/validation_helpers/uniqueness.cr index cbffe8fd..c3dee0a8 100644 --- a/src/granite/validation_helpers/uniqueness.cr +++ b/src/granite/validation_helpers/uniqueness.cr @@ -5,7 +5,7 @@ module Granite::ValidationHelpers instance = self.find_by({{field.id}}: model.{{field.id}}) - !(instance && instance.id != model.id) + !(instance && instance.id? != model.id?) end end end