From 07cdf97cce25c5c5b1e11e1d05e836547d1def1e Mon Sep 17 00:00:00 2001 From: Joakim Reinert Date: Sun, 1 Nov 2015 13:18:10 +0100 Subject: [PATCH 1/8] add shard.yml --- shard.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 shard.yml diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..1bed88a --- /dev/null +++ b/shard.yml @@ -0,0 +1,7 @@ +name: mongo.cr +version: 0.1.0 + +authors: + - Kent Sibilev + +license: MIT From 0198b64b2a63e905ecd807d2ecb4af09f0e6ff5e Mon Sep 17 00:00:00 2001 From: Joakim Reinert Date: Wed, 17 Feb 2016 03:52:01 +0100 Subject: [PATCH 2/8] replace deprecated methods to conform with crystal 0.11.0 --- src/bson/object_id.cr | 2 +- src/mongo/gridfs/fs.cr | 6 +++--- src/mongo/host.cr | 2 +- src/mongo/index_opt.cr | 6 +++--- src/mongo/uri.cr | 4 ++-- src/mongo/write_concern.cr | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/bson/object_id.cr b/src/bson/object_id.cr index b45a9c6..6e3ee43 100644 --- a/src/bson/object_id.cr +++ b/src/bson/object_id.cr @@ -7,7 +7,7 @@ class BSON def initialize(str : String) handle = Pointer(LibBSON::Oid).malloc(1) - LibBSON.bson_oid_init_from_string(handle, str.cstr) + LibBSON.bson_oid_init_from_string(handle, str.to_unsafe) initialize(handle) end diff --git a/src/mongo/gridfs/fs.cr b/src/mongo/gridfs/fs.cr index c713b7e..a672262 100644 --- a/src/mongo/gridfs/fs.cr +++ b/src/mongo/gridfs/fs.cr @@ -12,9 +12,9 @@ class Mongo::GridFS::FS # This function shall create a new file. def create_file(filename, content_type = nil, md5 = nil, aliases = BSON.new, metadata = BSON.new, chunk_size = 0) opt = LibMongoC::GFSFileOpt.new - opt.md5 = md5.cstr if md5 - opt.filename = filename.cstr - opt.content_type = content_type.cstr if content_type + opt.md5 = md5.to_unsafe if md5 + opt.filename = filename.to_unsafe + opt.content_type = content_type.to_unsafe if content_type opt.aliases = aliases.to_unsafe opt.metadata = metadata.to_unsafe opt.chunk_size = chunk_size.to_u32 diff --git a/src/mongo/host.cr b/src/mongo/host.cr index 225e3a7..7dab224 100644 --- a/src/mongo/host.cr +++ b/src/mongo/host.cr @@ -11,7 +11,7 @@ class Mongo::Host cur = handle loop do break if cur.nil? - hosts << Host.new(String.new(cur.value.host.buffer), cur.value.port, cur.value.family) + hosts << Host.new(String.new(cur.value.host.to_unsafe), cur.value.port, cur.value.family) cur = cur.value.next break if cur.nil? end diff --git a/src/mongo/index_opt.cr b/src/mongo/index_opt.cr index 648fec4..3f80f1f 100644 --- a/src/mongo/index_opt.cr +++ b/src/mongo/index_opt.cr @@ -20,7 +20,7 @@ class Mongo::IndexOpt @opt.background = @background @opt.unique = @unique if name = @name - @opt.name = name.cstr + @opt.name = name.to_unsafe end @opt.drop_dups = @drop_dups @opt.sparse = @sparse @@ -29,10 +29,10 @@ class Mongo::IndexOpt @opt.weights = weights.to_unsafe end if default_language = @default_language - @opt.default_language = default_language.cstr + @opt.default_language = default_language.to_unsafe end if language_override = @language_override - @opt.language_override = language_override.cstr + @opt.language_override = language_override.to_unsafe end pointerof(@opt) diff --git a/src/mongo/uri.cr b/src/mongo/uri.cr index df3359b..26d97ca 100644 --- a/src/mongo/uri.cr +++ b/src/mongo/uri.cr @@ -9,11 +9,11 @@ class Mongo::Uri end def initialize(uri) - initialize LibMongoC.uri_new(uri.cstr) + initialize LibMongoC.uri_new(uri.to_unsafe) end def initialize(host, port) - initialize LibMongoC.uri_new_for_host_port(host.cstr, port.to_u16) + initialize LibMongoC.uri_new_for_host_port(host.to_unsafe, port.to_u16) end def finalize diff --git a/src/mongo/write_concern.cr b/src/mongo/write_concern.cr index b5e857c..893cbb3 100644 --- a/src/mongo/write_concern.cr +++ b/src/mongo/write_concern.cr @@ -45,7 +45,7 @@ class Mongo::WriteConcern end def wtag=(value) - LibMongoC.write_concern_set_wtag(self, value.cstr) + LibMongoC.write_concern_set_wtag(self, value.to_unsafe) end def wtimeout From 4c4325b8c94ee85eca243dfa5412cf46ffdec2d1 Mon Sep 17 00:00:00 2001 From: Joakim Reinert Date: Wed, 17 Feb 2016 04:37:13 +0100 Subject: [PATCH 3/8] get rid of null byte in object id to_s method --- src/bson/object_id.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bson/object_id.cr b/src/bson/object_id.cr index 6e3ee43..a6fa49d 100644 --- a/src/bson/object_id.cr +++ b/src/bson/object_id.cr @@ -25,7 +25,7 @@ class BSON def to_s buf = StaticArray(UInt8, 25).new(0_u8) LibBSON.bson_oid_to_string(@handle, buf) - String.new(buf.to_slice) + String.new(buf.to_slice.to_unsafe) end def ==(other : ObjectId) From 92d7fc2aaacfa264a3258fcf06a2d795a91da6be Mon Sep 17 00:00:00 2001 From: Joakim Reinert Date: Wed, 24 Feb 2016 15:11:58 +0100 Subject: [PATCH 4/8] make cursor implement iterator --- spec/collection_spec.cr | 2 +- spec/database_spec.cr | 2 +- src/mongo/collection.cr | 3 ++- src/mongo/cursor.cr | 23 ++++------------------- 4 files changed, 8 insertions(+), 22 deletions(-) diff --git a/spec/collection_spec.cr b/spec/collection_spec.cr index fe59264..cf16a1f 100644 --- a/spec/collection_spec.cr +++ b/spec/collection_spec.cr @@ -40,7 +40,7 @@ describe Mongo::Collection do doc["name"].should eq("Bob") doc["age"].should eq(23) cursor.more.should be_true - cursor.next.should be_nil + cursor.next.should be_a(Iterator::Stop) cursor.more.should be_false col.remove({"name" => "Bob"}) col.count.should eq(0) diff --git a/spec/database_spec.cr b/spec/database_spec.cr index c7417b1..4711dd1 100644 --- a/spec/database_spec.cr +++ b/spec/database_spec.cr @@ -16,7 +16,7 @@ describe Mongo::Database do db.has_collection?("my_col").should be_true - col = db.find_collections.next.not_nil! + col = db.find_collections.next as BSON col["name"].should eq("my_col") db.collection_names.includes?("my_col").should be_true diff --git a/src/mongo/collection.cr b/src/mongo/collection.cr index 161bb95..8da0fd9 100644 --- a/src/mongo/collection.cr +++ b/src/mongo/collection.cr @@ -135,9 +135,10 @@ class Mongo::Collection def find_one(query, fields = BSON.new, flags = LibMongoC::QueryFlags::NONE, skip = 0, prefs = nil) cursor = find(query, fields, flags, skip: skip, prefs: prefs) - cursor.next.tap do + document = cursor.next.tap do cursor.close end + document.is_a?(Iterator::Stop) ? nil : document end # This method shall insert document into collection. If no _id element is diff --git a/src/mongo/cursor.cr b/src/mongo/cursor.cr index 95a8007..610c3ff 100644 --- a/src/mongo/cursor.cr +++ b/src/mongo/cursor.cr @@ -10,7 +10,7 @@ class Mongo::Cursor @closed = false end - include Enumerable(BSON) + include Iterator(BSON) def finalize close @@ -44,18 +44,9 @@ class Mongo::Cursor # next document. # It returns `nil` if the cursor was exhausted. def next - if LibMongoC.cursor_next(self, @data) - check_error - @current = BSON.copy_from @data.value - end - end - - # This method iterates the underlying cursor passing the resulted documents - # to the specified block. - def each - while v = self.next - yield v - end + return stop unless LibMongoC.cursor_next(self, @data) + check_error + BSON.copy_from @data.value end private def check_error @@ -78,12 +69,6 @@ class Mongo::Cursor LibMongoC.cursor_is_alive(self) end - # Fetches the cursors current document. - def current - check_closed - @current - end - def batch_size LibMongoC.cursor_get_batch_size(self) end From 3b2d5969c91d3ada45dbf14e7ff5a09f0a2be93c Mon Sep 17 00:00:00 2001 From: Joakim Reinert Date: Thu, 25 Feb 2016 02:08:16 +0100 Subject: [PATCH 5/8] add better to and from bson functionality and bson builder --- spec/bson_spec.cr | 175 +++++++++++++++++++++++++------------ src/bson.cr | 35 ++------ src/bson/appender.cr | 49 +++++++++++ src/bson/array_appender.cr | 13 --- src/bson/array_builder.cr | 22 +++++ src/bson/builder.cr | 18 ++++ src/bson/core_ext/array.cr | 19 ---- src/bson/core_ext/hash.cr | 19 ---- src/bson/core_ext/nil.cr | 5 -- src/bson/from_bson.cr | 45 ++++++++++ src/bson/to_bson.cr | 71 +++++++++++++++ 11 files changed, 330 insertions(+), 141 deletions(-) create mode 100644 src/bson/appender.cr delete mode 100644 src/bson/array_appender.cr create mode 100644 src/bson/array_builder.cr create mode 100644 src/bson/builder.cr delete mode 100644 src/bson/core_ext/array.cr delete mode 100644 src/bson/core_ext/hash.cr delete mode 100644 src/bson/core_ext/nil.cr create mode 100644 src/bson/from_bson.cr create mode 100644 src/bson/to_bson.cr diff --git a/spec/bson_spec.cr b/spec/bson_spec.cr index 0acae58..12a6f74 100644 --- a/spec/bson_spec.cr +++ b/spec/bson_spec.cr @@ -122,55 +122,6 @@ describe BSON do bson["ru"].should eq("привет") end - it "should be able to append document" do - bson = BSON.new - bson["v"] = 1 - bson.append_document("doc") do |child| - child["body"] = "document body" - end - - doc = bson["doc"] - if doc.is_a?(BSON) - doc.has_key?("body").should be_true - doc["body"].should eq("document body") - else - fail "doc must be BSON object" - end - end - - it "should invalidate child document after append" do - bson = BSON.new - bson["v"] = 1 - child = nil - bson.append_document("doc") do |child| - child.not_nil!["body"] = "document body" - end - expect_raises do - child.not_nil!["v"] = 2 - end - end - - it "should be able to append an array" do - bson = BSON.new - bson["v"] = 1 - bson.append_array("ary") do |child| - child << "a1" - child << "a2" - child << nil - child << 1 - end - - ary = bson["ary"] - if ary.is_a?(BSON) - ary.count.should eq(4) - ary["0"].should eq("a1") - ary["2"].should be_nil - ary["3"].should eq(1) - else - fail "ary must be BSON object" - end - end - it "should be able to append symbol" do bson = BSON.new bson["s"] = BSON::Symbol.new("symbol") @@ -313,16 +264,22 @@ describe BSON do end it "should be able to decode bson" do - bson = BSON.new - bson["x"] = 42 - bson.append_array("ary") do |child| - child << 1 - child << 2 - child << 3 - end - bson.append_document("doc") do |child| - child["y"] = "text" + bson = BSON.build do |doc| + doc.field("x", 42) + doc.field("ary") do |appender| + appender.array do |array| + array << 1 + array << 2 + array << 3 + end + end + doc.field("doc") do |appender| + appender.document do |doc| + doc.field("y", "text") + end + end end + h = {"x" => 42, "ary" => [1,2,3], "doc" => {"y" => "text"}} bson.decode.should eq(h) end @@ -335,4 +292,106 @@ describe BSON do fail "expected BSON" unless ary.is_a?(BSON) ary["0"].should eq(1) end + + context "build" do + it "yields a BSON::Builder" do + BSON.build do |builder| + builder.should be_a(BSON::Builder) + end + end + + it "returns a bson" do + BSON.build {}.should be_a(BSON) + end + + it "is able to add fields" do + bson = BSON.build do |doc| + doc.field("foo", "bar") + doc.field(:bar, "baz") + doc.field(1, "foobar") + end + + bson["foo"].should eq("bar") + bson["bar"].should eq("baz") + bson["1"].should eq("foobar") + end + + it "should be able to append document" do + bson = BSON.build do |doc| + doc.field("doc") do |appender| + appender.document do |child| + child.field("body", "document body") + end + end + end + + doc = bson["doc"] + if doc.is_a?(BSON) + doc.has_key?("body").should be_true + doc["body"].should eq("document body") + else + fail "doc must be BSON object" + end + end + + it "should invalidate child document after append" do + child_doc = nil + bson = BSON.build do |doc| + doc.field("doc") do |appender| + appender.document do |child| + child_doc = child.bson + child.field("body", "document body") + end + end + end + + expect_raises do + child_doc.not_nil!["v"] = 2 + end + end + + it "should be able to append an array" do + bson = BSON.build do |doc| + doc.field("ary") do |appender| + appender.array do |array| + array << "a1" + array << "a2" + array << nil + array << 1 + end + end + end + + ary = bson["ary"] + if ary.is_a?(BSON) + ary.count.should eq(4) + ary["0"].should eq("a1") + ary["2"].should be_nil + ary["3"].should eq(1) + else + fail "ary must be BSON object" + end + end + end + + context "from bson" do + it "creates an array from a bson array" do + bson = BSON.build_array do |doc| + doc << "foo" + doc << "bar" + doc << "baz" + end + + Array(String).new(bson).should eq(%w(foo bar baz)) + end + + it "creates a hash from a bson document" do + bson = BSON.build do |doc| + doc.field(:foo, "bar") + doc.field(:bar, "baz") + end + + Hash(String, String).new(bson).should eq({ "foo": "bar", "bar": "baz" }) + end + end end diff --git a/src/bson.cr b/src/bson.cr index 92dc69c..a1bd3d1 100644 --- a/src/bson.cr +++ b/src/bson.cr @@ -1,11 +1,18 @@ require "./bson/lib_bson" -require "./bson/core_ext/*" require "./bson/*" class BSON include Enumerable(Value) include Comparable(BSON) + def self.build(&block : Builder -> _) + Builder.new.tap(&block).bson + end + + def self.build_array(&block : ArrayBuilder -> _) + ArrayBuilder.new.tap(&block).bson + end + def initialize(@handle : LibBSON::BSON) @valid = true raise "invalid handle" unless @handle @@ -206,32 +213,6 @@ class BSON LibBSON.bson_append_regex(handle, key, key.bytesize, value.source, options) end - def append_document(key) - unless LibBSON.bson_append_document_begin(handle, key, key.bytesize, out child_handle) - return false - end - child = BSON.new(pointerof(child_handle)) - begin - yield child - ensure - LibBSON.bson_append_document_end(handle, child) - child.invalidate - end - end - - def append_array(key) - unless LibBSON.bson_append_array_begin(handle, key, key.bytesize, out child_handle) - return false - end - child = BSON.new(pointerof(child_handle)) - begin - yield ArrayAppender.new(child), child - ensure - LibBSON.bson_append_array_end(handle, child) - child.invalidate - end - end - def data data = LibBSON.bson_get_data(handle) Slice.new(data, handle.value.len.to_i32) diff --git a/src/bson/appender.cr b/src/bson/appender.cr new file mode 100644 index 0000000..e5075cb --- /dev/null +++ b/src/bson/appender.cr @@ -0,0 +1,49 @@ +class BSON + class Appender + getter? appended + def initialize(key, @bson) + @key = key.to_s + @appended = false + end + + def document + raise "already appended a value" if appended? + + unless LibBSON.bson_append_document_begin(@bson, @key, @key.bytesize, out child_handle) + return false + end + child = BSON.new(pointerof(child_handle)) + begin + yield Builder.new(child) + ensure + LibBSON.bson_append_document_end(@bson, child) + child.invalidate + @appended = true + end + end + + def array + raise "already appended a value" if appended? + + unless LibBSON.bson_append_array_begin(@bson, @key, @key.bytesize, out child_handle) + return false + end + + child = BSON.new(pointerof(child_handle)) + begin + yield ArrayBuilder.new(child) + ensure + LibBSON.bson_append_array_end(@bson, child) + child.invalidate + @appended = true + end + end + + def <<(value) + raise "already appended a value" if appended? + @bson[@key] = value + ensure + @appended = true + end + end +end diff --git a/src/bson/array_appender.cr b/src/bson/array_appender.cr deleted file mode 100644 index c39f9d6..0000000 --- a/src/bson/array_appender.cr +++ /dev/null @@ -1,13 +0,0 @@ -class BSON - struct ArrayAppender - def initialize(@bson) - @count = 0 - end - - def <<(value) - @bson[@count.to_s] = value - @count += 1 - self - end - end -end \ No newline at end of file diff --git a/src/bson/array_builder.cr b/src/bson/array_builder.cr new file mode 100644 index 0000000..ac2656e --- /dev/null +++ b/src/bson/array_builder.cr @@ -0,0 +1,22 @@ +class BSON + class ArrayBuilder + getter bson + def initialize(@bson = BSON.new) + @index = 0 + end + + def <<(value) + push(value) + end + + def push(value) + push { |appender| value.to_bson(appender) } + end + + def push + appender = Appender.new(@index, @bson) + yield appender + @index += 1 if appender.appended? + end + end +end diff --git a/src/bson/builder.cr b/src/bson/builder.cr new file mode 100644 index 0000000..a78a612 --- /dev/null +++ b/src/bson/builder.cr @@ -0,0 +1,18 @@ +class BSON + class Builder + getter bson + + def initialize(@bson = BSON.new) + end + + def field(key, value) + field(key) { |appender| value.to_bson(appender) } + end + + def field(key) + appender = Appender.new(key, @bson) + yield appender + appender << nil unless appender.appended? + end + end +end diff --git a/src/bson/core_ext/array.cr b/src/bson/core_ext/array.cr deleted file mode 100644 index fd30488..0000000 --- a/src/bson/core_ext/array.cr +++ /dev/null @@ -1,19 +0,0 @@ -class Array(T) - def to_bson(bson = BSON.new) - each_with_index do |item, i| - case item - when Array - bson.append_array(i.to_s) do |appender, child| - item.to_bson(child) - end - when Hash - bson.append_document(i.to_s) do |child| - item.to_bson(child) - end - else - bson[i.to_s] = item - end - end - bson - end -end \ No newline at end of file diff --git a/src/bson/core_ext/hash.cr b/src/bson/core_ext/hash.cr deleted file mode 100644 index f7cd405..0000000 --- a/src/bson/core_ext/hash.cr +++ /dev/null @@ -1,19 +0,0 @@ -class Hash(K, V) - def to_bson(bson = BSON.new) - each do |k, v| - case v - when Array - bson.append_array(k) do |appender, child| - v.to_bson(child) - end - when Hash - bson.append_document(k) do |child| - v.to_bson(child) - end - else - bson[k] = v - end - end - bson - end -end \ No newline at end of file diff --git a/src/bson/core_ext/nil.cr b/src/bson/core_ext/nil.cr deleted file mode 100644 index 4cbe979..0000000 --- a/src/bson/core_ext/nil.cr +++ /dev/null @@ -1,5 +0,0 @@ -struct Nil - def to_bson - self - end -end \ No newline at end of file diff --git a/src/bson/from_bson.cr b/src/bson/from_bson.cr new file mode 100644 index 0000000..7ffde41 --- /dev/null +++ b/src/bson/from_bson.cr @@ -0,0 +1,45 @@ +def Array.new(bson_value : BSON::Value) + bson = bson_value.value + unless bson.is_a?(BSON) + raise "this bson value is not a bson object - it's #{typeof(bson)}" + end + + new(bson) +end + +def Array.new(bson : BSON) + raise "this bson is not an array" unless bson.array? + + array = new + + bson.each do |bson_value| + array << T.new(bson_value) + end + + array +end + +def Hash.new(bson_value : BSON::Value) + bson = bson_value.value + unless bson.is_a?(BSON) + raise "this bson value is not a bson object - it's #{typeof(bson)}" + end + + new(bson) +end + +def Hash.new(bson : BSON) + hash = new + + bson.each_pair do |key, bson_value| + hash[key] = V.new(bson_value) + end + + hash +end + +{% for type in [Int32, Int64, Bool, Float64, Nil, String, Time, Regex, String] %} + def {{type}}.new(bson_value : BSON::Value) + bson_value.value as {{type}} + end +{% end %} diff --git a/src/bson/to_bson.cr b/src/bson/to_bson.cr new file mode 100644 index 0000000..dbf8154 --- /dev/null +++ b/src/bson/to_bson.cr @@ -0,0 +1,71 @@ +{% for type in [Int32, Int64, Bool, Float64, Float32, Nil, Time] %} + struct {{type}} + def to_bson(appender) + appender << self + end + end +{% end %} + +{% for type in %w(Symbol MinKey MaxKey ObjectId Timestamp Code Binary) %} + struct BSON::{{type.id}} + def to_bson(appender) + appender << self + end + end +{% end %} + +{% for type in [String, BSON, Regex] %} + class {{type}} + def to_bson(appender) + appender << self + end + end +{% end %} + +struct Nil + def to_bson + self + end +end + +struct Symbol + def to_bson(appender) + appender << BSON::Symbol.new(self.to_s) + end +end + +class Array(T) + def to_bson + BSON.build_array do |array| + each do |value| + array << value + end + end + end + + def to_bson(appender) + appender.array do |array| + each do |value| + array << value + end + end + end +end + +class Hash(K, V) + def to_bson + BSON.build do |doc| + each do |k, v| + doc.field(k.to_s, v) + end + end + end + + def to_bson(appender) + appender.document do |doc| + each do |k, v| + doc.field(k.to_s, v) + end + end + end +end From b741ad1ccb97d788f7e61c42ca47fa569c878193 Mon Sep 17 00:00:00 2001 From: Joakim Reinert Date: Thu, 25 Feb 2016 02:38:15 +0100 Subject: [PATCH 6/8] add bson mapping --- spec/bson_spec.cr | 31 ++++++++++++ spec/spec_helper.cr | 30 +++++++++++ src/bson/mapping.cr | 120 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 src/bson/mapping.cr diff --git a/spec/bson_spec.cr b/spec/bson_spec.cr index 12a6f74..f6a5acd 100644 --- a/spec/bson_spec.cr +++ b/spec/bson_spec.cr @@ -394,4 +394,35 @@ describe BSON do Hash(String, String).new(bson).should eq({ "foo": "bar", "bar": "baz" }) end end + + context "mapping" do + it "produces a bson with all specified attributes" do + mapping = TestMapping.new("bar", OtherTestMapping.new("foo")) + bson = mapping.to_bson + bson["foo"].should eq("bar") + bson["baz"].should eq(0) + bar = bson["bar"] + if bar.is_a?(BSON) + bar["foobar"].should eq("foo - TEST") + else + fail "bar must be a BSON object" + end + end + + it "maps all attributes from a bson" do + bson = BSON.build do |doc| + doc.field :foo, "bar" + doc.field :bar do |appender| + appender.document do |doc| + doc.field :foobar, "foo" + end + end + end + + mapping = TestMapping.new(bson) + mapping.foo.should eq("bar") + mapping.bar.foobar.should eq("foo") + mapping.baz.should eq(0) + end + end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 7377711..7e6a5f6 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -28,4 +28,34 @@ def with_collection end end +module TestConverter + extend self + def to_bson(bson_value, appender) + appender << "#{bson_value} - TEST" + end + + def from_bson(bson_value) + bson_value.value as String + end +end + +class OtherTestMapping + BSON.mapping({ + foobar: { type: String, converter: TestConverter } + }) + + def initialize(@foobar : String) + end +end + +class TestMapping + BSON.mapping({ + foo: String, + bar: OtherTestMapping, + baz: { type: Int32, default: 0 } + }) + + def initialize(@foo, @bar, @baz = 0) + end +end diff --git a/src/bson/mapping.cr b/src/bson/mapping.cr new file mode 100644 index 0000000..8b01487 --- /dev/null +++ b/src/bson/mapping.cr @@ -0,0 +1,120 @@ +class BSON + macro mapping(properties, strict = false) + {% for key, value in properties %} + {% properties[key] = {type: value} unless value.is_a?(HashLiteral) %} + {% end %} + + {% for key, value in properties %} + @{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }} + + def {{key.id}}=(_{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }}) + @{{key.id}} = _{{key.id}} + end + + def {{key.id}} + @{{key.id}} + end + {% end %} + + def self.new(bson_value : BSON::Value) + %value = bson_value.value + unless %value.is_a?(BSON) + raise "expected value to be a bson object. was #{typeof(%value)}" + end + + new(%value) + end + + def initialize(%bson : BSON) + {% for key, value in properties %} + %var{key.id} = nil + %found{key.id} = false + {% end %} + + %bson.each_pair do |key, value| + case key + {% for key, value in properties %} + when {{value[:key] || key.id.stringify}} + %found{key.id} = true + %var{key.id} = + {% if value[:nilable] || value[:default] != nil %} + value.value.try { + {% end %} + {% if value[:converter] %} + {{value[:converter]}}.from_bson(value) + {% else %} + {{value[:type]}}.new(value) + {% end %} + {% if value[:nilable] || value[:default] != nil %} + } + {% end %} + {% end %} + else + {% if strict %} + raise "unknown bson attribute: #{key}" + {% end %} + end + end + + {% for key, value in properties %} + {% unless value[:nilable] || value[:default] != nil %} + if %var{key.id}.is_a?(Nil) && !%found{key.id} + raise "missing bson attribute: {{(value[:key] || key).id}}" + end + {% end %} + {% end %} + + {% for key, value in properties %} + {% if value[:nilable] %} + {% if value[:default] != nil %} + @{{key.id}} = %found{key.id} ? %var{key.id} : {{value[:default]}} + {% else %} + @{{key.id}} = %var{key.id} + {% end %} + {% elsif value[:default] != nil %} + @{{key.id}} = %var{key.id}.is_a?(Nil) ? {{value[:default]}} : %var{key.id} + {% else %} + @{{key.id}} = %var{key.id}.not_nil! + {% end %} + {% end %} + end + + def to_bson + BSON.build do |doc| + append_to_bson_document(doc) + end + end + + def to_bson(appender) + appender.document do |doc| + append_to_bson_document(doc) + end + end + + private def append_to_bson_document(doc) + {% for key, value in properties %} + _{{key.id}} = @{{key.id}} + + {% unless value[:emit_null] %} + unless _{{key.id}}.is_a?(Nil) + {% end %} + + doc.field({{value[:key] || key.id.stringify}}) do |appender| + {% if value[:converter] %} + if _{{key.id}} + {{value[:converter]}}.to_bson(_{{key.id}}, appender) + else + appender << nil + end + {% else %} + _{{key.id}}.to_bson(appender) + {% end %} + end + + {% unless value[:emit_null] %} + end + {% end %} + {% end %} + end + end +end From 900296f36aecf7fcb99cde78310a75d3b2f7f932 Mon Sep 17 00:00:00 2001 From: Joakim Reinert Date: Sat, 27 Feb 2016 22:08:18 +0100 Subject: [PATCH 7/8] make sure keys are mapped as strings in bson mapping --- spec/bson_spec.cr | 5 ++++- spec/spec_helper.cr | 5 +++-- src/bson/mapping.cr | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/bson_spec.cr b/spec/bson_spec.cr index f6a5acd..116d334 100644 --- a/spec/bson_spec.cr +++ b/spec/bson_spec.cr @@ -397,9 +397,10 @@ describe BSON do context "mapping" do it "produces a bson with all specified attributes" do - mapping = TestMapping.new("bar", OtherTestMapping.new("foo")) + mapping = TestMapping.new("bar", OtherTestMapping.new("foo"), "one") bson = mapping.to_bson bson["foo"].should eq("bar") + bson["two"].should eq("one") bson["baz"].should eq(0) bar = bson["bar"] if bar.is_a?(BSON) @@ -417,11 +418,13 @@ describe BSON do doc.field :foobar, "foo" end end + doc.field :two, "one" end mapping = TestMapping.new(bson) mapping.foo.should eq("bar") mapping.bar.foobar.should eq("foo") + mapping.one.should eq("one") mapping.baz.should eq(0) end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 7e6a5f6..f664141 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -53,9 +53,10 @@ class TestMapping BSON.mapping({ foo: String, bar: OtherTestMapping, - baz: { type: Int32, default: 0 } + one: { type: String, key: :two }, + baz: { type: Int32, default: 0 }, }) - def initialize(@foo, @bar, @baz = 0) + def initialize(@foo, @bar, @one, @baz = 0) end end diff --git a/src/bson/mapping.cr b/src/bson/mapping.cr index 8b01487..de3bcc9 100644 --- a/src/bson/mapping.cr +++ b/src/bson/mapping.cr @@ -34,7 +34,7 @@ class BSON %bson.each_pair do |key, value| case key {% for key, value in properties %} - when {{value[:key] || key.id.stringify}} + when {{(value[:key] || key).id.stringify}} %found{key.id} = true %var{key.id} = {% if value[:nilable] || value[:default] != nil %} From bb56e298e9e2bbbb11e8551bd4dbc9b2d5279230 Mon Sep 17 00:00:00 2001 From: Joakim Reinert Date: Tue, 15 Mar 2016 12:21:55 +0100 Subject: [PATCH 8/8] reflect breaking changes in crystal compiler --- src/mongo/read_prefs.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mongo/read_prefs.cr b/src/mongo/read_prefs.cr index fdbf6c6..5fea521 100644 --- a/src/mongo/read_prefs.cr +++ b/src/mongo/read_prefs.cr @@ -3,7 +3,7 @@ class Mongo::ReadPrefs raise "invalid handle" unless @handle end - def initialize(mode = LibMongoC::ReadMode::PRIMARY : LibMongoC::ReadMode) + def initialize(mode : LibMongoC::ReadMode = LibMongoC::ReadMode::PRIMARY) initialize LibMongoC.read_prefs_new(mode) end