kvom 6.8.0.beta.200.566.d1df6eb

Sign up to get free protection for your applications and to get access to all the features.
@@ -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