kvom 6.8.0.beta.200.566.d1df6eb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "pry"
4
+ gem "yajl-ruby"
5
+
6
+ # Specify your gem's dependencies in kvom.gemspec
7
+ gemspec
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../../rake_support/git_based_version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "kvom"
6
+ s.version = RakeSupport.git_based_version(__FILE__)
7
+ s.authors = ["Kristian Hanekamp, Infopark AG"]
8
+ s.email = ["kristian.hanekamp@infopark.de"]
9
+ s.homepage = ""
10
+ s.summary = %q{Key Value Object Mapper}
11
+ s.description = %q{Use it to build object models in ruby on top of a key value store.}
12
+
13
+ s.rubyforge_project = "kvom"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_development_dependency "rspec"
21
+ s.add_development_dependency "helpful_configuration"
22
+
23
+ s.add_runtime_dependency "couchrest"
24
+ s.add_runtime_dependency "activemodel"
25
+ s.add_runtime_dependency "aws-sdk"
26
+ end
@@ -0,0 +1,3 @@
1
+ require "kvom/adapter/base"
2
+ require "kvom/document"
3
+ require "kvom/base"
@@ -0,0 +1,43 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+
3
+ module Kvom; module Adapter
4
+
5
+ class Base
6
+ attr_reader :connection_spec
7
+
8
+ attr_reader :documents_loaded_counter, :request_counter
9
+
10
+ def initialize(connection_spec_raw)
11
+ @connection_spec = connection_spec_raw.symbolize_keys
12
+ @documents_loaded_counter = @request_counter = 0
13
+ end
14
+
15
+ def new_document(key, attributes)
16
+ raise "implement me in subclass!"
17
+ end
18
+
19
+ def save(doc)
20
+ raise "implement me in subclass!"
21
+ end
22
+
23
+ def get(key)
24
+ raise "implement me in subclass!"
25
+ end
26
+
27
+ def destroy(doc)
28
+ raise "implement me in subclass!"
29
+ end
30
+
31
+ protected
32
+
33
+ def count_request(num_requests = 1)
34
+ @request_counter += num_requests
35
+ result = yield
36
+ @documents_loaded_counter += Array === result ? result.size : 1
37
+ result
38
+ end
39
+
40
+ end
41
+
42
+ end ; end # module Kvom
43
+
@@ -0,0 +1,82 @@
1
+ require 'couchrest'
2
+ require 'kvom/adapter/couchdb_document'
3
+
4
+ module Kvom; module Adapter
5
+
6
+ class CouchdbAdapter < Kvom::Adapter::Base
7
+ def new_document(key, attributes)
8
+ new_document_from_attributes(attributes.merge("_id" => key))
9
+ end
10
+
11
+ def save(doc)
12
+ doc.couchrest_document.save
13
+ end
14
+
15
+ def get(key)
16
+ doc =
17
+ begin
18
+ count_request {database.get(key) }
19
+ rescue RestClient::ResourceNotFound
20
+ raise Kvom::NotFound.for_key(key)
21
+ end
22
+ CouchdbDocument.new(doc)
23
+ end
24
+
25
+ def query(hash_value, range_value)
26
+ params =
27
+ case range_value
28
+ when Range
29
+ {
30
+ :startkey => "#{hash_value}|#{range_value.begin}",
31
+ :endkey => "#{hash_value}|#{range_value.end}",
32
+ }
33
+ else
34
+ {
35
+ :key => "#{hash_value}|#{range_value}",
36
+ }
37
+ end
38
+
39
+ params[:include_docs] = true
40
+ count_request {database.all_docs(params)["rows"]}.map do |entry|
41
+ new_document_from_attributes(entry["doc"])
42
+ end
43
+ end
44
+
45
+ def destroy(doc)
46
+ doc.couchrest_document.destroy
47
+ end
48
+
49
+ module BlobSupport
50
+
51
+ def blob_url(id)
52
+ "#{ database_url }/#{ id }/blob"
53
+ end
54
+
55
+ def blob_attachment(id)
56
+ document = count_request {database.get(id)}
57
+ document["_attachments"]["blob"]
58
+ end
59
+
60
+ end
61
+
62
+ include BlobSupport
63
+
64
+ private
65
+
66
+ def database_url
67
+ connection_spec[:url]
68
+ end
69
+
70
+ def new_document_from_attributes(attributes)
71
+ couch_doc = CouchRest::Document.new(attributes)
72
+ couch_doc.database = database
73
+ CouchdbDocument.new(couch_doc)
74
+ end
75
+
76
+ def database
77
+ @database ||= CouchRest.database(database_url)
78
+ end
79
+
80
+ end
81
+
82
+ end ; end # module Kvom
@@ -0,0 +1,23 @@
1
+ module Kvom; module Adapter
2
+
3
+ class CouchdbDocument < Document
4
+ attr_accessor :couchrest_document
5
+
6
+ def initialize(couchrest_document)
7
+ self.couchrest_document = couchrest_document
8
+ end
9
+
10
+ def [](name)
11
+ couchrest_document[name]
12
+ end
13
+
14
+ def []=(name, value)
15
+ couchrest_document[name] = value
16
+ end
17
+
18
+ def key
19
+ couchrest_document.id
20
+ end
21
+ end
22
+
23
+ end ; end # module Kvom
@@ -0,0 +1,116 @@
1
+ require 'aws-sdk'
2
+ require 'kvom/adapter/dynamodb_document'
3
+
4
+ module Kvom; module Adapter
5
+
6
+ class DynamodbAdapter < Kvom::Adapter::Base
7
+ def new_document(key, attributes = nil)
8
+ DynamodbDocument.new_document(attributes, key, item_for_key(key))
9
+ end
10
+
11
+ def get(key)
12
+ item = item_for_key(key)
13
+ count_request {item.exists?} or raise Kvom::NotFound.for_key(key)
14
+ DynamodbDocument.document_with_item(key, item)
15
+ end
16
+
17
+ def save(doc)
18
+ doc.save
19
+ end
20
+
21
+ def destroy(doc)
22
+ doc.destroy
23
+ end
24
+
25
+ def query(hash_value, range_value)
26
+ count_request do
27
+ case range_value
28
+ when String
29
+ item = item_for_hash_and_range(hash_value, range_value)
30
+ attributes = item.attributes.to_h
31
+ if attributes.empty?
32
+ []
33
+ else
34
+ key = key_from_item(item)
35
+ [DynamodbDocument.document_from_query(attributes, key, item)]
36
+ end
37
+ when Range
38
+ dynamo_range_start = dynamo_range_value(range_value.begin)
39
+ dynamo_range_end = dynamo_range_value(range_value.end)
40
+
41
+ query_options = {
42
+ :hash_value => partitioned_hash_value(hash_value),
43
+ :range_value => Range.new(dynamo_range_start, dynamo_range_end),
44
+ :select => :all,
45
+ }
46
+ table.items.query(query_options).map do |item_data|
47
+ item = item_data.item
48
+ key = key_from_item(item)
49
+ DynamodbDocument.document_from_query(item_data.attributes, key, item)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def table
58
+ @table ||= begin
59
+ # TODO define dynamo endpoint externally?
60
+ AWS.config(:dynamo_db_endpoint => 'dynamodb.eu-west-1.amazonaws.com')
61
+
62
+ # TODO: explicit nil testing
63
+ credentials = {
64
+ :access_key_id => connection_spec[:access_key_id],
65
+ :secret_access_key => connection_spec[:secret_access_key],
66
+ }
67
+ table = AWS::DynamoDB.new(credentials).tables[connection_spec[:table]]
68
+ table.hash_key = [:hash_key, :string]
69
+ table.range_key = [:range_key, :string]
70
+ table
71
+ end
72
+ end
73
+
74
+ def key_from_item(item)
75
+ hash_value, range_value = item.hash_value, item.range_value
76
+ range_value = "" if range_value == " " # range_from_dynamo_range
77
+ hash_value = hash_value[(partition.length + 1)..-1] if partition # unpartitioned_hash_value
78
+ [hash_value, range_value].join("|")
79
+ end
80
+
81
+ def item_for_key(key)
82
+ item_for_hash_and_range(*(key.split("|", 2)))
83
+ end
84
+
85
+ def item_for_hash_and_range(hash, range)
86
+ table.items[partitioned_hash_value(hash), dynamo_item_range_value(range)]
87
+ end
88
+
89
+ def dynamo_item_range_value(range_value)
90
+ # conflict with the empty range value
91
+ range_value == " " and raise "Unexpected range <single space>"
92
+ dynamo_range_value(range_value)
93
+ end
94
+
95
+ def dynamo_range_value(range_value)
96
+ case range_value || ""
97
+ when ""
98
+ # range_key is required and must not be empty
99
+ " "
100
+ else
101
+ range_value
102
+ end
103
+ end
104
+
105
+ def partitioned_hash_value(hash_value)
106
+ return hash_value unless partition
107
+ "#{partition}|#{hash_value}"
108
+ end
109
+
110
+ def partition
111
+ @partition ||= connection_spec[:partition]
112
+ end
113
+
114
+ end
115
+
116
+ end ; end # module Kvom
@@ -0,0 +1,159 @@
1
+ module Kvom; module Adapter
2
+
3
+ class DynamodbDocument < Document
4
+ class << self
5
+ def new_document(attributes, key, item)
6
+ new(key, item, :attributes => attributes)
7
+ end
8
+
9
+ def document_with_item(key, item)
10
+ new(key, item)
11
+ end
12
+
13
+ def document_from_query(attributes, key, item)
14
+ new(key, item, :item_attributes => attributes)
15
+ end
16
+
17
+ end
18
+
19
+ attr_reader :key
20
+
21
+ def initialize(key, item, provided_attributes = nil)
22
+ @key = key
23
+ @dynamo_item = item
24
+ if provided_attributes
25
+ if provided_attributes.key?(:attributes)
26
+ @kv = provided_attributes[:attributes] || {}
27
+ @modified = true
28
+ elsif provided_attributes.key?(:item_attributes)
29
+ @kv = attributes_from_item_attributes(provided_attributes[:item_attributes])
30
+ end
31
+ end
32
+ end
33
+
34
+ def [](name)
35
+ kv[name]
36
+ end
37
+
38
+ def []=(name, value)
39
+ case value
40
+ when String, Fixnum, nil
41
+ # okay ...
42
+ else
43
+ raise "Unsupported value type: #{value.class.name}"
44
+ end
45
+ @modified = true
46
+ kv[name] = value
47
+ end
48
+
49
+ def save
50
+ return unless modified?
51
+ dynamo_attributes = @kv.inject({}) do |memo, (key, value)|
52
+ case value
53
+ when nil
54
+ # skip
55
+ when String, Fixnum, BigDecimal
56
+ memo[key] = value
57
+ else
58
+ raise "Unsupported value type #{value.class.to_s}"
59
+ end
60
+ memo
61
+ end
62
+ dynamo_attributes["hash_key"] = @dynamo_item.hash_value
63
+ dynamo_attributes["range_key"] = @dynamo_item.range_value
64
+ dynamo_attributes["@rev"] = new_revision = (@rev || 0) + 1
65
+ # might be improved to saving only changed attributes
66
+ @dynamo_item.table.items.put(dynamo_attributes, revision_condition)
67
+ @rev = new_revision
68
+ @modified = false
69
+ end
70
+
71
+ def destroy
72
+ @dynamo_item.delete(revision_condition)
73
+ end
74
+
75
+ private
76
+
77
+ def modified?
78
+ @modified
79
+ end
80
+
81
+ def attributes_from_item_attributes(item_attributes)
82
+ attributes = AttributesFromItemAttributes.from(item_attributes)
83
+ @rev = attributes["@rev"]
84
+ attributes
85
+ end
86
+
87
+ def kv
88
+ @kv ||= attributes_from_item_attributes(@dynamo_item.attributes.to_h)
89
+ end
90
+
91
+ def revision
92
+ @rev
93
+ end
94
+
95
+ def revision_condition
96
+ dynamo_condition("@rev" => @rev)
97
+ end
98
+
99
+ def dynamo_condition(condition)
100
+ present_condition = {}
101
+ missing_condition = []
102
+ condition.each do |(key, value)|
103
+ if value
104
+ present_condition[key] = value
105
+ else
106
+ missing_condition << key
107
+ end
108
+ end
109
+ options = {}
110
+ options[:if] = present_condition unless present_condition.empty?
111
+ options[:unless] = missing_condition unless missing_condition.empty?
112
+ options
113
+ end
114
+
115
+ class AttributesFromItemAttributes
116
+ def self.from(attributes)
117
+ attributes.delete("hash_key")
118
+ attributes.delete("range_key")
119
+ meta = attributes.delete("@meta.json")
120
+ return attributes unless meta
121
+ meta = MultiJson.decode(meta)
122
+ coding = meta["coding"]
123
+ return attributes if !coding || coding.empty?
124
+ new(attributes, coding)
125
+ end
126
+
127
+ def initialize(attributes, coding)
128
+ @attributes = attributes
129
+ @coding = coding
130
+ end
131
+
132
+ def [](name)
133
+ value = @attributes[name]
134
+ coding = @coding[name]
135
+ return value unless coding
136
+ coding == "json" or raise "Unexpected coding #{coding} for attribute #{name}"
137
+ value = parse_json_value(value)
138
+ self[name] = value
139
+ end
140
+
141
+ def []=(name, value)
142
+ @coding.delete(name)
143
+ @attributes[name] = value
144
+ end
145
+
146
+ private
147
+
148
+ def parse_json_value(value)
149
+ case value[0]
150
+ when ?{
151
+ MultiJson.decode(value)
152
+ else
153
+ MultiJson.decode("{\"key\":#{value}}")["key"]
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ end ; end # module Kvom
@@ -0,0 +1,116 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/core_ext/class/attribute'
4
+ require 'active_model/naming'
5
+ require 'securerandom'
6
+
7
+ require 'kvom/model_identity'
8
+
9
+ module Kvom
10
+
11
+ class NotFound < ::StandardError
12
+ def self.for_key(key)
13
+ new(%(document with key "#{key}" not found))
14
+ end
15
+ end
16
+
17
+ class Base
18
+ include ModelIdentity
19
+ extend ActiveModel::Naming
20
+
21
+ class_attribute :key_prefix
22
+
23
+ class << self
24
+ def create(attributes = {})
25
+ new(attributes).tap { |model| model.save }
26
+ end
27
+
28
+ def find(id)
29
+ raise "no id given" if id.blank?
30
+ new(find_document(id))
31
+ end
32
+
33
+ def find_by_id(id)
34
+ find(id)
35
+ rescue NotFound
36
+ nil
37
+ end
38
+
39
+ def property(name_raw)
40
+ name = name_raw.to_s
41
+
42
+ define_method name do
43
+ read_attribute(name)
44
+ end
45
+ define_method "#{name}=" do |value|
46
+ write_attribute(name, value)
47
+ end
48
+ end
49
+
50
+ # default implementation for the class_attribute
51
+ def key_prefix
52
+ to_s
53
+ end
54
+
55
+ def adapter
56
+ raise "must be overwritten in subclasses"
57
+ end
58
+
59
+ def key_for(id)
60
+ "#{key_prefix}/id/#{id}|"
61
+ end
62
+
63
+ private
64
+
65
+ def find_document(id)
66
+ adapter.get(key_for(id))
67
+ end
68
+
69
+ end
70
+
71
+ def initialize(doc_or_attrs = {})
72
+ if doc_or_attrs.is_a?(Document)
73
+ @document = doc_or_attrs
74
+ else
75
+ attrs = doc_or_attrs.stringify_keys
76
+
77
+ attrs["id"] ||= SecureRandom.hex(8)
78
+
79
+ id = attrs["id"]
80
+ @document = self.class.adapter.new_document(self.class.key_for(id), attrs)
81
+ @new = true
82
+ end
83
+ end
84
+
85
+ def document
86
+ @document
87
+ end
88
+
89
+ def save
90
+ self.class.adapter.save(@document)
91
+ @new = false
92
+ end
93
+
94
+ def id
95
+ @document["id"]
96
+ end
97
+
98
+ def read_attribute(name)
99
+ @document[name]
100
+ end
101
+
102
+ def write_attribute(name, value)
103
+ @document[name] = value
104
+ end
105
+
106
+ def new?
107
+ @new
108
+ end
109
+
110
+ def destroy
111
+ self.class.adapter.destroy(@document)
112
+ end
113
+ end
114
+
115
+ end # module Kvom
116
+
@@ -0,0 +1,17 @@
1
+ module Kvom
2
+
3
+ class Document
4
+ def key
5
+ raise "implement me in subclass!"
6
+ end
7
+
8
+ def [](name)
9
+ raise "implement me in subclass!"
10
+ end
11
+
12
+ def []=(name, value)
13
+ raise "implement me in subclass!"
14
+ end
15
+ end
16
+
17
+ end # module Kvom
@@ -0,0 +1,23 @@
1
+ module Kvom
2
+
3
+ module ModelIdentity # :nodoc:
4
+ # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
5
+ # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
6
+ def ==(comparison_object)
7
+ comparison_object.equal?(self) ||
8
+ (comparison_object.instance_of?(self.class) && comparison_object.id == id)
9
+ end
10
+
11
+ # Delegates to ==
12
+ def eql?(comparison_object)
13
+ self == (comparison_object)
14
+ end
15
+
16
+ # Delegates to id in order to allow two records of the same type and id to work with something like:
17
+ # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
18
+ def hash
19
+ id.hash
20
+ end
21
+ end
22
+
23
+ end # module Kvom
@@ -0,0 +1,217 @@
1
+ require 'spec_helper'
2
+
3
+ class CustomKeyPrefixModel < TestModel
4
+ self.key_prefix = "spam_prefix"
5
+ end
6
+
7
+ class ModelWithoutAdapter < Kvom::Base
8
+ end
9
+
10
+ def random_id
11
+ rand.to_s
12
+ end
13
+
14
+ describe Kvom::Base do
15
+ it "should complain when no adapter is specified" do
16
+ expect { ModelWithoutAdapter.find("spam") }.to raise_error(/overwritten/)
17
+ end
18
+
19
+ it "should provide access to the underlying document" do
20
+ example = ExampleModel.create(:spam => "foo")
21
+ example.document["spam"].should == "foo"
22
+ example.document.should be_a(Kvom::Document)
23
+ end
24
+
25
+ describe "getters and setters" do
26
+ it "work for freshly created models" do
27
+ example = ExampleModel.create(:spam => "foo")
28
+ example.spam.should == "foo"
29
+ example.spam = "bar"
30
+ example.spam.should == "bar"
31
+ end
32
+
33
+ it "handle access via string as well as symbols" do
34
+ example = ExampleModel.create(:spam => "foo")
35
+ example.spam.should == "foo"
36
+ example = ExampleModel.create("spam" => "foo")
37
+ example.spam.should == "foo"
38
+ end
39
+
40
+ it "works for models loaded from database" do
41
+ example = ExampleModel.create(:spam => "foo")
42
+ example.save
43
+
44
+ loaded = ExampleModel.find(example.id)
45
+ loaded.spam.should == "foo"
46
+ end
47
+ end
48
+
49
+ it "should accept an id as string or symbol" do
50
+ ExampleModel.create(:id => (first_id = random_id))
51
+ ExampleModel.create("id" => (second_id = random_id))
52
+ ExampleModel.find(first_id)
53
+ ExampleModel.find(second_id)
54
+ end
55
+
56
+ describe "model id's" do
57
+ it "should use a custom prefix when given" do
58
+ CustomKeyPrefixModel.new.document.key.should =~ /^spam_prefix\/id\/.+/
59
+ end
60
+
61
+ it "should use a default prefix" do
62
+ ExampleModel.new.document.key.should =~ /^ExampleModel\/id\/.+/
63
+ end
64
+ end
65
+
66
+ describe "BaseModel#find" do
67
+ it "should refuse to find with an empty id" do
68
+ expect { ExampleModel.find("") }.to raise_error /no id given/
69
+ end
70
+
71
+ it "should find model instances" do
72
+ example = ExampleModel.create(:spam => "foo")
73
+ ExampleModel.find(example.id).id.should == example.id
74
+ ExampleModel.find(example.id).spam.should == "foo"
75
+ end
76
+
77
+ it "should raise exception when instances cannot be found" do
78
+ model_id = random_id
79
+ expect {
80
+ ExampleModel.find(model_id)
81
+ }.to raise_error(Kvom::NotFound, %r(document.*"ExampleModel/id/#{model_id}|"/))
82
+ end
83
+ end
84
+
85
+ describe "BaseModel#find_by_id" do
86
+ context "when called with an empty id" do
87
+ it "raises an error complaining about the missing id" do
88
+ expect { ExampleModel.find_by_id("") }.to raise_error /no id given/
89
+ end
90
+ end
91
+
92
+ context "when called with an id of an existing model" do
93
+ let(:existing_model) {
94
+ ExampleModel.create
95
+ }
96
+
97
+ it "returns the model instance" do
98
+ id = existing_model.id
99
+ result = ExampleModel.find_by_id(id)
100
+ result.should be_instance_of(ExampleModel)
101
+ result.id.should == id
102
+ end
103
+ end
104
+
105
+ context "when called with an id where no model exists for" do
106
+ it "should return nil" do
107
+ ExampleModel.find_by_id(random_id).should be_nil
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "destroying models" do
113
+
114
+ context "when the model has been created without id" do
115
+
116
+ let(:model_without_specified_id) {ExampleModel.create}
117
+
118
+ it "destroys the model" do
119
+ id = model_without_specified_id.id
120
+ expect {
121
+ model_without_specified_id.destroy
122
+ }.to change {
123
+ ExampleModel.find_by_id(id)
124
+ }.to nil
125
+ end
126
+
127
+ end
128
+
129
+ context "when the model has been created with an id" do
130
+
131
+ let(:model_with_id) {ExampleModel.create(:id => rand.to_s)}
132
+
133
+ it "destroys the model" do
134
+ id = model_with_id.id
135
+ expect {
136
+ model_with_id.destroy
137
+ }.to change {
138
+ ExampleModel.find_by_id(id)
139
+ }.to nil
140
+ end
141
+
142
+ end
143
+
144
+ context "when the model instance has been loaded from database" do
145
+
146
+ let(:model_from_db) {ExampleModel.find_by_id(ExampleModel.create.id)}
147
+
148
+ it "destroys the model" do
149
+ id = model_from_db.id
150
+ expect {
151
+ model_from_db.destroy
152
+ }.to change {
153
+ ExampleModel.find_by_id(id)
154
+ }.to nil
155
+ end
156
+
157
+ end
158
+
159
+ end
160
+
161
+ describe "#new?" do
162
+
163
+ context "when instantiated from attributes" do
164
+
165
+ let(:model_from_attributes) {ExampleModel.new({})}
166
+
167
+ it "is new" do
168
+ model_from_attributes.should be_new
169
+ end
170
+
171
+ context "and saved" do
172
+
173
+ before do
174
+ model_from_attributes.save
175
+ end
176
+
177
+ it "is not new" do
178
+ model_from_attributes.should_not be_new
179
+ end
180
+
181
+ end
182
+
183
+ context "including id" do
184
+
185
+ let(:model_id) {"static" + rand.to_s}
186
+ let(:model_from_attributes_including_id) {ExampleModel.new({"id" => model_id})}
187
+
188
+ before do
189
+ model_from_attributes_including_id.id.should == model_id
190
+ end
191
+
192
+ it "is new" do
193
+ model_from_attributes.should be_new
194
+ end
195
+
196
+ end
197
+ end
198
+
199
+ context "when instantiated from database" do
200
+
201
+ let(:model_from_database) {ExampleModel.find_by_id(ExampleModel.create.id)}
202
+
203
+ it "returns falsy" do
204
+ model_from_database.should_not be_new
205
+ end
206
+ end
207
+ end
208
+
209
+
210
+ it "should use ModelIdentity" do
211
+ ExampleModel.should include(Kvom::ModelIdentity)
212
+ end
213
+
214
+ it "should be extended to use ActiveModel::Naming " do
215
+ ExampleModel.model_name.should == "ExampleModel"
216
+ end
217
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kvom::Base do
4
+ let(:model) { ExampleModel.create }
5
+
6
+ it "should count the number of requests" do
7
+ expect {
8
+ ExampleModel.find(model.id)
9
+ }.to change(ExampleModel.adapter, :request_counter).by(1)
10
+ end
11
+
12
+ it "should count the number of document loaded" do
13
+ expect {
14
+ ExampleModel.find(model.id)
15
+ }.to change(ExampleModel.adapter, :documents_loaded_counter).by(1)
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ require "kvom/model_identity"
4
+
5
+ describe Kvom::ModelIdentity do
6
+ class IdTest
7
+ include Kvom::ModelIdentity
8
+ attr_accessor :id
9
+
10
+ def initialize(id)
11
+ self.id = id
12
+ end
13
+ end
14
+
15
+ it "should be identical iff it's id is identical" do
16
+ IdTest.new("foo").should == IdTest.new("foo")
17
+ IdTest.new("foo").should_not == IdTest.new("bar")
18
+ end
19
+
20
+ it "should be eql iff it's id is identical" do
21
+ IdTest.new("foo").should be_eql IdTest.new("foo")
22
+ IdTest.new("foo").should_not be_eql IdTest.new("bar")
23
+ end
24
+
25
+ it "should be usable as hash keys" do
26
+ hash = {IdTest.new("foo") => "FOO", IdTest.new("bar") => "BAR"}
27
+ hash[IdTest.new("foo")].should == "FOO"
28
+ hash[IdTest.new("bar")].should == "BAR"
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ $LOAD_PATH.unshift(Pathname(__FILE__) + "../lib")
2
+
3
+ require 'pry'
4
+
5
+ require 'kvom'
6
+
7
+ class TestModel < Kvom::Base
8
+ def self.adapter
9
+ @adapter ||=
10
+ case (type = ENV['KVOM_ADAPTER_TYPE'] || "couch")
11
+ when "couch"
12
+ require "kvom/adapter/couchdb_adapter"
13
+ require 'multi_json'
14
+ MultiJson.engine = :yajl
15
+ database_name = "kvom-unit_tests"
16
+ CouchRest.database!(database_name)
17
+ Kvom::Adapter::CouchdbAdapter.new({
18
+ :url => database_name,
19
+ })
20
+ when /dynamo/
21
+ require "kvom/adapter/dynamodb_adapter"
22
+ require File.expand_path("../../../tasks/support/local_config", __FILE__)
23
+ options = {
24
+ :table => "test-kvom",
25
+ :access_key_id => local_config["aws_access_key_id"],
26
+ :secret_access_key => local_config["aws_secret_access_key"],
27
+ }
28
+ options[:partition] = "korb" if type == "partitioned_dynamo"
29
+ Kvom::Adapter::DynamodbAdapter.new(options)
30
+ end
31
+ end
32
+ end
33
+
34
+ class ExampleModel < TestModel
35
+ property :spam
36
+ end
37
+
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kvom
3
+ version: !ruby/object:Gem::Version
4
+ hash: -215055160403
5
+ prerelease: 6
6
+ segments:
7
+ - 6
8
+ - 8
9
+ - 0
10
+ - beta
11
+ - 200
12
+ - 566
13
+ - d
14
+ - 1
15
+ - df
16
+ - 6
17
+ - eb
18
+ version: 6.8.0.beta.200.566.d1df6eb
19
+ platform: ruby
20
+ authors:
21
+ - Kristian Hanekamp, Infopark AG
22
+ autorequire:
23
+ bindir: bin
24
+ cert_chain: []
25
+
26
+ date: 2012-04-26 00:00:00 +02:00
27
+ default_executable:
28
+ dependencies:
29
+ - !ruby/object:Gem::Dependency
30
+ name: rspec
31
+ prerelease: false
32
+ requirement: &id001 !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ hash: 3
38
+ segments:
39
+ - 0
40
+ version: "0"
41
+ type: :development
42
+ version_requirements: *id001
43
+ - !ruby/object:Gem::Dependency
44
+ name: helpful_configuration
45
+ prerelease: false
46
+ requirement: &id002 !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ hash: 3
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ type: :development
56
+ version_requirements: *id002
57
+ - !ruby/object:Gem::Dependency
58
+ name: couchrest
59
+ prerelease: false
60
+ requirement: &id003 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ type: :runtime
70
+ version_requirements: *id003
71
+ - !ruby/object:Gem::Dependency
72
+ name: activemodel
73
+ prerelease: false
74
+ requirement: &id004 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ hash: 3
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: aws-sdk
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 3
94
+ segments:
95
+ - 0
96
+ version: "0"
97
+ type: :runtime
98
+ version_requirements: *id005
99
+ description: Use it to build object models in ruby on top of a key value store.
100
+ email:
101
+ - kristian.hanekamp@infopark.de
102
+ executables: []
103
+
104
+ extensions: []
105
+
106
+ extra_rdoc_files: []
107
+
108
+ files:
109
+ - .gitignore
110
+ - Gemfile
111
+ - Rakefile
112
+ - kvom.gemspec
113
+ - lib/kvom.rb
114
+ - lib/kvom/adapter/base.rb
115
+ - lib/kvom/adapter/couchdb_adapter.rb
116
+ - lib/kvom/adapter/couchdb_document.rb
117
+ - lib/kvom/adapter/dynamodb_adapter.rb
118
+ - lib/kvom/adapter/dynamodb_document.rb
119
+ - lib/kvom/base.rb
120
+ - lib/kvom/document.rb
121
+ - lib/kvom/model_identity.rb
122
+ - spec/base_spec.rb
123
+ - spec/counter_spec.rb
124
+ - spec/model_identity_spec.rb
125
+ - spec/spec_helper.rb
126
+ has_rdoc: true
127
+ homepage: ""
128
+ licenses: []
129
+
130
+ post_install_message:
131
+ rdoc_options: []
132
+
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ hash: 3
141
+ segments:
142
+ - 0
143
+ version: "0"
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ">"
148
+ - !ruby/object:Gem::Version
149
+ hash: 25
150
+ segments:
151
+ - 1
152
+ - 3
153
+ - 1
154
+ version: 1.3.1
155
+ requirements: []
156
+
157
+ rubyforge_project: kvom
158
+ rubygems_version: 1.6.2
159
+ signing_key:
160
+ specification_version: 3
161
+ summary: Key Value Object Mapper
162
+ test_files:
163
+ - spec/base_spec.rb
164
+ - spec/counter_spec.rb
165
+ - spec/model_identity_spec.rb
166
+ - spec/spec_helper.rb