data_broker 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +50 -0
- data/Rakefile +1 -0
- data/data_broker.gemspec +30 -0
- data/lib/data_broker.rb +21 -0
- data/lib/data_broker/mapper.rb +112 -0
- data/lib/data_broker/value_object.rb +59 -0
- data/lib/data_broker/version.rb +3 -0
- data/spec/mapper_spec.rb +213 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/value_object_spec.rb +169 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ab94b51d25a3a4822f72e55571c65a91093a59db
|
4
|
+
data.tar.gz: 8b934b53c08ca0ef7679fd34cb7863edf425032f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 57adee8f7db28230cf4b9b1b3f8e7b49e6f2c5a4ea1a7cdb52799db9b12a8651c09fee1b33002008728ff2553265423ddf760e8906697e632c560828ab6f7b84
|
7
|
+
data.tar.gz: 3b67351d428611e9902165b78317023dd1c8c6aab6ccce3cc0efbdfb430eb7cdd8eb8127aa3109878e1e986b0600a71d41b71ac61244606a4ffbfa3fc30c6426
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 TeamSnap
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# DataBroker
|
2
|
+
|
3
|
+
[![Semaphore](https://semaphoreapp.com/api/v1/projects/20b511c80e766c8d5d897140b7f7d87df8e4da9b/118941/shields_badge.png)](https://semaphoreapp.com/minter/data_broker)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/teamsnap/data_broker.png)](https://codeclimate.com/github/teamsnap/data_broker)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/teamsnap/data_broker/badge.png?branch=master)](https://coveralls.io/r/teamsnap/data_broker?branch=master)
|
6
|
+
[![Dependency Status](https://gemnasium.com/teamsnap/data_broker.png)](https://gemnasium.com/teamsnap/data_broker)
|
7
|
+
[![License](http://img.shields.io/license/MIT.png?color=green)](http://opensource.org/licenses/MIT)
|
8
|
+
|
9
|
+
A simplified implementation of the data mapper pattern
|
10
|
+
|
11
|
+
![Data Broker](http://i.imgur.com/p01gSKM.jpg)
|
12
|
+
|
13
|
+
Tired of the Active Record pattern forcing your design decision? Working with a legacy database that doesn't conform to Rails golden path? Then perhaps you need the [data mapper pattern](http://martinfowler.com/eaaCatalog/dataMapper.html).
|
14
|
+
|
15
|
+
This library implements an extremely simplified version of the data mapper pattern. It allows you to map POROs (backed by Virtus) to arbitrary tables via ActiveRecord. The DataMapper gem provides this functionality, but not for Ruby 2.x/Rails 4.x. and the alternatives such as datamappify (lots of magic inferances that don't work well with legacy systems) and Ruby Object Mapper (not yet available for production) don't fit our needs at TeamSnap.
|
16
|
+
|
17
|
+
Currently DataBroker only allows for a 1-to-1 mapping of objects to tables. In the future we plan to add 1-to-many mappings of objects to tables.
|
18
|
+
|
19
|
+
The interface for DataBroker is purposefully lightweight and similar to both datamappify and ruby object mapper so that you can migrate between them with ease.
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
gem 'data_broker'
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install data_broker
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
View specs for sample usage.
|
38
|
+
|
39
|
+
## Roadmap
|
40
|
+
|
41
|
+
- Allow 1-to-many mappings of objects to tables
|
42
|
+
- Implement the Unit of Work pattern (i.e. transactions) around 1-to-many mappings
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
1. Fork it
|
47
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
48
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
49
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
50
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/data_broker.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'data_broker/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "data_broker"
|
8
|
+
spec.version = DataBroker::VERSION
|
9
|
+
spec.authors = ["Shane Emmons"]
|
10
|
+
spec.email = ["oss@teamsnap.com"]
|
11
|
+
spec.description = "A simplified implementation of the data mapper pattern"
|
12
|
+
spec.summary = "A simplified implementation of the data mapper pattern"
|
13
|
+
spec.homepage = "https://github.com/teamsnap/data_broker"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "virtus", "~> 1.0"
|
22
|
+
spec.add_dependency "activesupport", "~> 4.0"
|
23
|
+
spec.add_dependency "activerecord", "~> 4.0"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0.0.beta1"
|
28
|
+
spec.add_development_dependency "sqlite3", "~> 1.3"
|
29
|
+
spec.add_development_dependency "database_cleaner", "~> 1.2"
|
30
|
+
end
|
data/lib/data_broker.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "data_broker/mapper"
|
2
|
+
require_relative "data_broker/value_object"
|
3
|
+
require_relative "data_broker/version"
|
4
|
+
|
5
|
+
module DataBroker
|
6
|
+
def self.mapper
|
7
|
+
Module.new do
|
8
|
+
def self.included(descendant)
|
9
|
+
descendant.send(:include, ::DataBroker::Mapper)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.value_object
|
15
|
+
Module.new do
|
16
|
+
def self.included(descendant)
|
17
|
+
descendant.send(:include, ::DataBroker::ValueObject)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
module DataBroker
|
4
|
+
module Mapper
|
5
|
+
def self.included(descendant)
|
6
|
+
super
|
7
|
+
descendant.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
attr_accessor :mapping, :relations
|
12
|
+
|
13
|
+
def relations
|
14
|
+
@relations || {}
|
15
|
+
end
|
16
|
+
|
17
|
+
[:record, :model].each do |attr|
|
18
|
+
ivar = :"@#{attr}"
|
19
|
+
define_method(:"#{attr}=") do |value|
|
20
|
+
instance_variable_set(ivar, value)
|
21
|
+
end
|
22
|
+
|
23
|
+
define_method(:"#{attr}") do
|
24
|
+
instance_variable_get(ivar) || infer(attr)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def all
|
29
|
+
record_to_object(self.record.all)
|
30
|
+
end
|
31
|
+
|
32
|
+
def find(id)
|
33
|
+
record = eager_record.find_by(:id => id)
|
34
|
+
record.nil? ? nil : record_to_object(record, :eager_load => true)
|
35
|
+
end
|
36
|
+
|
37
|
+
def where(conds)
|
38
|
+
conds = Hash[conds.map { |key, value| [self.mapping[key], value] }]
|
39
|
+
record_to_object(
|
40
|
+
eager_record.where(conds), :eager_load => true
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def save(model)
|
45
|
+
record = self.record.find_by(:id => model.id) || self.record.new
|
46
|
+
attributes = Hash[self.mapping.map { |key, value| [value, model[key]] }]
|
47
|
+
attributes.delete(:id)
|
48
|
+
record.attributes = attributes
|
49
|
+
|
50
|
+
record.save!
|
51
|
+
record_to_object(record)
|
52
|
+
end
|
53
|
+
|
54
|
+
def destroy(model)
|
55
|
+
record = self.record.find_by(:id => model.id)
|
56
|
+
record.nil? ? nil : record_to_object(record.destroy)
|
57
|
+
end
|
58
|
+
|
59
|
+
def record_to_object(record, eager_load: false)
|
60
|
+
if record.respond_to?(:each)
|
61
|
+
record.compact.map { |rec| record_to_object(rec, :eager_load => eager_load) }
|
62
|
+
else
|
63
|
+
attributes = Hash[self.mapping.map { |key, value| [key, record[value]] }]
|
64
|
+
self.model.new(attributes).tap do |model|
|
65
|
+
eager_load_relations(model, record) if eager_load
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def eager_load_relations(model, record)
|
73
|
+
self.relations.each do |model_ref, record_ref|
|
74
|
+
rec = record.send(record_ref)
|
75
|
+
if rec
|
76
|
+
ref_mapper = mapper_for(model_ref)
|
77
|
+
ref = ref_mapper.record_to_object(rec)
|
78
|
+
model.instance_variable_set(:"@#{model_ref}", ref)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def mapper_for(model_ref)
|
84
|
+
"#{model_ref.to_s.singularize}_mapper".camelize.safe_constantize
|
85
|
+
end
|
86
|
+
|
87
|
+
def inferred_basename
|
88
|
+
self.to_s.underscore.sub(/_mapper$/, "").singularize.camelize
|
89
|
+
end
|
90
|
+
|
91
|
+
def inferred_model
|
92
|
+
inferred_basename.safe_constantize
|
93
|
+
end
|
94
|
+
|
95
|
+
def infer(attr)
|
96
|
+
if attr == :model
|
97
|
+
inferred_model
|
98
|
+
else
|
99
|
+
"#{inferred_basename}#{attr.to_s.camelize}".safe_constantize
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def eager_record
|
104
|
+
self.record.includes(record_relations)
|
105
|
+
end
|
106
|
+
|
107
|
+
def record_relations
|
108
|
+
self.relations.values
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "virtus"
|
2
|
+
require "active_support/inflector"
|
3
|
+
require "active_support/core_ext/object/try"
|
4
|
+
|
5
|
+
module DataBroker
|
6
|
+
module ValueObject
|
7
|
+
def self.included(descendant)
|
8
|
+
descendant.send(:include, Virtus.value_object)
|
9
|
+
descendant.send(:include, ActiveSupport::Inflector)
|
10
|
+
descendant.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
include ActiveSupport::Inflector
|
15
|
+
|
16
|
+
def belongs_to(method)
|
17
|
+
mapper = "#{camelize(method.to_s)}Mapper"
|
18
|
+
via = foreign_key(camelize(method.to_s))
|
19
|
+
|
20
|
+
define_method(method) do
|
21
|
+
ivar = "@#{method}"
|
22
|
+
instance_variable_get(ivar) || instance_variable_set(
|
23
|
+
ivar, constantize(mapper).find(self.send(via))
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_many(method)
|
29
|
+
mapper = "#{classify(method.to_s)}Mapper"
|
30
|
+
via = foreign_key(self.to_s)
|
31
|
+
ids_method = pluralize(foreign_key(classify(method.to_s)))
|
32
|
+
|
33
|
+
define_method(method) do
|
34
|
+
ivar = "@#{method}"
|
35
|
+
instance_variable_get(ivar) || instance_variable_set(
|
36
|
+
ivar, constantize(mapper).where(via.to_sym => self.id)
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
define_method(ids_method) do
|
41
|
+
ivar = "@#{ids_method}"
|
42
|
+
instance_variable_get(ivar) || instance_variable_set(
|
43
|
+
ivar, send(method).map(&:id)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def delegate(method, to: nil)
|
49
|
+
delegated = "#{to}_#{method}"
|
50
|
+
define_method(delegated) do
|
51
|
+
ivar = "@#{delegated}"
|
52
|
+
instance_variable_get(ivar) || instance_variable_set(
|
53
|
+
ivar, send(to).try(method)
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/spec/mapper_spec.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require "sqlite3"
|
3
|
+
require_relative "../lib/data_broker"
|
4
|
+
|
5
|
+
class Parent
|
6
|
+
include DataBroker.value_object
|
7
|
+
|
8
|
+
values do
|
9
|
+
attribute :id, Integer
|
10
|
+
end
|
11
|
+
|
12
|
+
has_many :children
|
13
|
+
end
|
14
|
+
|
15
|
+
class Child
|
16
|
+
include DataBroker.value_object
|
17
|
+
|
18
|
+
values do
|
19
|
+
attribute :id, Integer
|
20
|
+
attribute :name, String
|
21
|
+
attribute :parent_id, Integer
|
22
|
+
end
|
23
|
+
|
24
|
+
belongs_to :parent
|
25
|
+
end
|
26
|
+
|
27
|
+
class ParentRecord < ActiveRecord::Base
|
28
|
+
self.table_name = :parents
|
29
|
+
has_many :children, :class_name => "ChildRecord",
|
30
|
+
:foreign_key => :parent_id
|
31
|
+
end
|
32
|
+
|
33
|
+
class ChildRecord < ActiveRecord::Base
|
34
|
+
self.table_name = :children
|
35
|
+
belongs_to :parent, :class_name => "ParentRecord",
|
36
|
+
:foreign_key => :parent_id
|
37
|
+
end
|
38
|
+
|
39
|
+
class ParentMapper
|
40
|
+
include DataBroker.mapper
|
41
|
+
|
42
|
+
self.mapping = {
|
43
|
+
:id => :id
|
44
|
+
}
|
45
|
+
|
46
|
+
self.relations = {
|
47
|
+
:children => :children
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
class ChildMapper
|
52
|
+
include DataBroker.mapper
|
53
|
+
|
54
|
+
self.mapping = {
|
55
|
+
:id => :id,
|
56
|
+
:name => :child_name,
|
57
|
+
:parent_id => :parent_id
|
58
|
+
}
|
59
|
+
|
60
|
+
self.relations = {
|
61
|
+
:parent => :parent
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
describe DataBroker::Mapper do
|
66
|
+
let(:mapper) { ChildMapper }
|
67
|
+
let(:parent_mapper) { ParentMapper }
|
68
|
+
|
69
|
+
it "infers record if not given" do
|
70
|
+
expect(mapper.record).to eq(ChildRecord)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "infers model if not given" do
|
74
|
+
expect(mapper.model).to eq(Child)
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "#all" do
|
78
|
+
it "uses the record to fetch all entries" do
|
79
|
+
3.times { mapper.record.create }
|
80
|
+
|
81
|
+
objs = mapper.all
|
82
|
+
expect(objs.size).to eq(3)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "returns objects cast as the model" do
|
86
|
+
mapper.record.create
|
87
|
+
obj = mapper.all.first
|
88
|
+
|
89
|
+
expect(obj).to be_a(mapper.model)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#find" do
|
94
|
+
it "finds the record matching the given primary key" do
|
95
|
+
id = mapper.record.create(:child_name => "Team Jacob").id
|
96
|
+
|
97
|
+
obj = mapper.find(id)
|
98
|
+
expect(obj.id).to eq(id)
|
99
|
+
expect(obj.name).to eq("Team Jacob")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "returns the object cast as the model" do
|
103
|
+
id = mapper.record.create.id
|
104
|
+
|
105
|
+
obj = mapper.find(id)
|
106
|
+
expect(obj).to be_a(mapper.model)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "returns nil if the object isn't found" do
|
110
|
+
obj = mapper.find(0)
|
111
|
+
expect(obj).to be_nil
|
112
|
+
end
|
113
|
+
|
114
|
+
it "eager loads relations" do
|
115
|
+
parent_id = parent_mapper.record.create.id
|
116
|
+
id = mapper.record.create(:parent_id => parent_id)
|
117
|
+
obj = mapper.find(id)
|
118
|
+
|
119
|
+
parent = obj.instance_variable_get(:@parent)
|
120
|
+
expect(parent).to eq(parent_mapper.find(parent_id))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#where" do
|
125
|
+
it "returns and empty array if conds yield no results" do
|
126
|
+
objs = mapper.where(:name => "Team Jacob")
|
127
|
+
expect(objs).to be_empty
|
128
|
+
end
|
129
|
+
|
130
|
+
it "returns the objects cast as the model" do
|
131
|
+
id = mapper.record.create.id
|
132
|
+
obj = mapper.where(:id => id).first
|
133
|
+
|
134
|
+
expect(obj).to be_a(mapper.model)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "returns the objects matching the conds" do
|
138
|
+
id = mapper.record.create(:child_name => "Team Jacob").id
|
139
|
+
obj = mapper.where(:id => id).first
|
140
|
+
|
141
|
+
expect(obj.id).to eq(id)
|
142
|
+
expect(obj.name).to eq("Team Jacob")
|
143
|
+
end
|
144
|
+
|
145
|
+
it "uses the mappings to translate model attrs to db columns" do
|
146
|
+
id = mapper.record.create(:child_name => "Team Jacob").id
|
147
|
+
obj = mapper.where(:name => "Team Jacob").first
|
148
|
+
|
149
|
+
expect(obj.id).to eq(id)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "returns all matches" do
|
153
|
+
3.times { |n| mapper.record.create(:child_name => "Team Jacob") }
|
154
|
+
objs = mapper.where(:name => "Team Jacob")
|
155
|
+
|
156
|
+
expect(objs.size).to eq(3)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "eager loads relations" do
|
160
|
+
parent_id = parent_mapper.record.create.id
|
161
|
+
id = mapper.record.create(:parent_id => parent_id)
|
162
|
+
obj = mapper.where(:id => id).first
|
163
|
+
|
164
|
+
parent = obj.instance_variable_get(:@parent)
|
165
|
+
expect(parent).to eq(parent_mapper.find(parent_id))
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "#save" do
|
170
|
+
it "translates a model and saves it as a record" do
|
171
|
+
mapper.save(mapper.model.new(:name => "Team Jacob"))
|
172
|
+
ds = mapper.record.where(:child_name => "Team Jacob")
|
173
|
+
|
174
|
+
expect(ds).to_not be_empty
|
175
|
+
end
|
176
|
+
|
177
|
+
it "returns the model" do
|
178
|
+
model = mapper.model.new(:name => "Team Jacob")
|
179
|
+
rt = mapper.save(model)
|
180
|
+
|
181
|
+
expect(rt.name).to eq(model.name)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "updates an existing model" do
|
185
|
+
model = mapper.save(mapper.model.new)
|
186
|
+
model = model.with(:name => "Team Jacob")
|
187
|
+
rt = mapper.save(model)
|
188
|
+
|
189
|
+
expect(rt.id).to eq(model.id)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe "#destroy" do
|
194
|
+
it "returns nil if the object doesn't exist" do
|
195
|
+
obj = mapper.destroy(mapper.model.new(:id => 0))
|
196
|
+
expect(obj).to be_nil
|
197
|
+
end
|
198
|
+
|
199
|
+
it "returns the destroyed object" do
|
200
|
+
model = mapper.save(mapper.model.new)
|
201
|
+
rt = mapper.destroy(model)
|
202
|
+
|
203
|
+
expect(model).to eq(rt)
|
204
|
+
end
|
205
|
+
|
206
|
+
it "destroys the object" do
|
207
|
+
model = mapper.save(mapper.model.new)
|
208
|
+
mapper.destroy(model)
|
209
|
+
|
210
|
+
expect(mapper.find(model.id)).to be_nil
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "coveralls"
|
2
|
+
Coveralls.wear!
|
3
|
+
|
4
|
+
require "active_record"
|
5
|
+
require "database_cleaner"
|
6
|
+
|
7
|
+
ActiveRecord::Base.establish_connection(
|
8
|
+
:adapter => "sqlite3", :database => ":memory:"
|
9
|
+
)
|
10
|
+
|
11
|
+
ActiveRecord::Migration.create_table :parents
|
12
|
+
ActiveRecord::Migration.create_table :children do |t|
|
13
|
+
t.string :child_name
|
14
|
+
t.references :parent
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.before(:suite) do
|
19
|
+
DatabaseCleaner.strategy = :transaction
|
20
|
+
DatabaseCleaner.clean_with(:truncation)
|
21
|
+
end
|
22
|
+
|
23
|
+
config.before(:each) do
|
24
|
+
DatabaseCleaner.start
|
25
|
+
end
|
26
|
+
|
27
|
+
config.after(:each) do
|
28
|
+
DatabaseCleaner.clean
|
29
|
+
end
|
30
|
+
|
31
|
+
config.order = "random"
|
32
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require_relative "../lib/data_broker"
|
3
|
+
|
4
|
+
class TestValueObject
|
5
|
+
include DataBroker.value_object
|
6
|
+
|
7
|
+
values do
|
8
|
+
attribute :id, Integer
|
9
|
+
attribute :parent_id, Integer
|
10
|
+
end
|
11
|
+
|
12
|
+
belongs_to :parent
|
13
|
+
|
14
|
+
has_many :friends
|
15
|
+
|
16
|
+
delegate :name, :to => :parent
|
17
|
+
end
|
18
|
+
|
19
|
+
describe DataBroker::ValueObject do
|
20
|
+
describe ".new" do
|
21
|
+
it "creates a new object" do
|
22
|
+
obj = TestValueObject.new
|
23
|
+
expect(obj).to be_a(TestValueObject)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "accepts attributes in the constructor" do
|
27
|
+
obj = TestValueObject.new(:id => 1, :parent_id => 2)
|
28
|
+
expect(obj.id).to eq(1)
|
29
|
+
expect(obj.parent_id).to eq(2)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#with" do
|
34
|
+
it "creates a copy of the object with updated attributes" do
|
35
|
+
obj1 = TestValueObject.new(:id => 1, :parent_id => 2)
|
36
|
+
obj2 = obj1.with(:id => 2)
|
37
|
+
|
38
|
+
expect(obj1).to_not eq(obj2)
|
39
|
+
expect(obj2.id).to eq(2)
|
40
|
+
expect(obj2.parent_id).to eq(2)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe ".belongs_to" do
|
45
|
+
it "adds a lookup method" do
|
46
|
+
obj = TestValueObject.new
|
47
|
+
expect(obj).to respond_to(:parent)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "figures out what mapper and column to use, and uses them" do
|
51
|
+
obj = TestValueObject.new(:parent_id => 1)
|
52
|
+
mapper = double("ParentMapper")
|
53
|
+
allow(obj).to receive(:constantize).with("ParentMapper") { mapper }
|
54
|
+
expect(mapper).to receive(:find).with(1)
|
55
|
+
|
56
|
+
obj.parent
|
57
|
+
end
|
58
|
+
|
59
|
+
it "memoizes the results" do
|
60
|
+
obj = TestValueObject.new(:parent_id => 1)
|
61
|
+
mapper = double("ParentMapper", :find => :foo)
|
62
|
+
allow(obj).to receive(:constantize).with("ParentMapper") { mapper }
|
63
|
+
|
64
|
+
2.times { obj.parent }
|
65
|
+
expect(mapper).to have_received(:find).once
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe ".has_many" do
|
70
|
+
context "lookup method" do
|
71
|
+
it "adds a lookup method" do
|
72
|
+
obj = TestValueObject.new
|
73
|
+
expect(obj).to respond_to(:friends)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "figures out what mapper and via to use, and uses them" do
|
77
|
+
obj = TestValueObject.new(:id => 1)
|
78
|
+
mapper = double("FriendMapper")
|
79
|
+
allow(obj).to receive(:constantize).with("FriendMapper") { mapper }
|
80
|
+
expect(mapper).to receive(:where).with(:test_value_object_id => 1)
|
81
|
+
|
82
|
+
obj.friends
|
83
|
+
end
|
84
|
+
|
85
|
+
it "memoizes results" do
|
86
|
+
obj = TestValueObject.new(:id => 1)
|
87
|
+
mapper = double("FriendMapper", :where => :foo)
|
88
|
+
allow(obj).to receive(:constantize).with("FriendMapper") { mapper }
|
89
|
+
|
90
|
+
2.times { obj.friends }
|
91
|
+
expect(mapper).to have_received(:where).once
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "id lookup method" do
|
96
|
+
it "add an id lookup method" do
|
97
|
+
obj = TestValueObject.new
|
98
|
+
expect(obj).to respond_to(:friend_ids)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "gets the ids of objects found via the lookup method" do
|
102
|
+
obj = TestValueObject.new(:id => 1)
|
103
|
+
mapper = double("FriendMapper")
|
104
|
+
allow(obj).to receive(:constantize).with("FriendMapper") { mapper }
|
105
|
+
expect(mapper).to receive(:where).with(:test_value_object_id => 1) {
|
106
|
+
[double(:id => 1), double(:id => 2)]
|
107
|
+
}
|
108
|
+
|
109
|
+
expect(obj.friend_ids).to eq([1,2])
|
110
|
+
end
|
111
|
+
|
112
|
+
it "memoizes results" do
|
113
|
+
obj = TestValueObject.new(:id => 1)
|
114
|
+
results = [double(:id => 1), double(:id => 2)]
|
115
|
+
mapper = double("FriendMapper", :where => results)
|
116
|
+
allow(obj).to receive(:constantize).with("FriendMapper") { mapper }
|
117
|
+
|
118
|
+
2.times { obj.friend_ids }
|
119
|
+
expect(mapper).to have_received(:where).once
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe ".delegate" do
|
125
|
+
it "creates a delegated method" do
|
126
|
+
obj = TestValueObject.new
|
127
|
+
expect(obj).to respond_to(:parent_name)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "sends the message to the delegated object" do
|
131
|
+
obj = TestValueObject.new
|
132
|
+
parent = double(:name => "Bobby")
|
133
|
+
allow(obj).to receive(:parent) { parent }
|
134
|
+
|
135
|
+
expect(obj.parent_name).to eq("Bobby")
|
136
|
+
end
|
137
|
+
|
138
|
+
it "returns nil if the delegated object is nil" do
|
139
|
+
obj = TestValueObject.new
|
140
|
+
allow(obj).to receive(:parent) { nil }
|
141
|
+
|
142
|
+
expect(obj.parent_name).to be_nil
|
143
|
+
end
|
144
|
+
|
145
|
+
it "memoizes the results" do
|
146
|
+
obj = TestValueObject.new
|
147
|
+
allow(obj).to receive(:parent) { double(:name => true) }
|
148
|
+
|
149
|
+
2.times { obj.parent_name }
|
150
|
+
expect(obj).to have_received(:parent).once
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "#==" do
|
155
|
+
it "considers objects with the same attributes to be equivalent" do
|
156
|
+
obj1 = TestValueObject.new(:id => 1, :parent_id => 2)
|
157
|
+
obj2 = TestValueObject.new(:id => 1, :parent_id => 2)
|
158
|
+
|
159
|
+
expect(obj1).to eq(obj2)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "considers objects with non-matching attributes to not be equivalent" do
|
163
|
+
obj1 = TestValueObject.new(:id => 1, :parent_id => 2)
|
164
|
+
obj2 = TestValueObject.new(:id => 2, :parent_id => 2)
|
165
|
+
|
166
|
+
expect(obj1).to_not eq(obj2)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: data_broker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shane Emmons
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: virtus
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.0.0.beta1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.0.0.beta1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.3'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.3'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: database_cleaner
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.2'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.2'
|
125
|
+
description: A simplified implementation of the data mapper pattern
|
126
|
+
email:
|
127
|
+
- oss@teamsnap.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- .gitignore
|
133
|
+
- Gemfile
|
134
|
+
- LICENSE.txt
|
135
|
+
- README.md
|
136
|
+
- Rakefile
|
137
|
+
- data_broker.gemspec
|
138
|
+
- lib/data_broker.rb
|
139
|
+
- lib/data_broker/mapper.rb
|
140
|
+
- lib/data_broker/value_object.rb
|
141
|
+
- lib/data_broker/version.rb
|
142
|
+
- spec/mapper_spec.rb
|
143
|
+
- spec/spec_helper.rb
|
144
|
+
- spec/value_object_spec.rb
|
145
|
+
homepage: https://github.com/teamsnap/data_broker
|
146
|
+
licenses:
|
147
|
+
- MIT
|
148
|
+
metadata: {}
|
149
|
+
post_install_message:
|
150
|
+
rdoc_options: []
|
151
|
+
require_paths:
|
152
|
+
- lib
|
153
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
requirements: []
|
164
|
+
rubyforge_project:
|
165
|
+
rubygems_version: 2.0.14
|
166
|
+
signing_key:
|
167
|
+
specification_version: 4
|
168
|
+
summary: A simplified implementation of the data mapper pattern
|
169
|
+
test_files:
|
170
|
+
- spec/mapper_spec.rb
|
171
|
+
- spec/spec_helper.rb
|
172
|
+
- spec/value_object_spec.rb
|