data_broker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "coveralls", :require => false
4
+
5
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module DataBroker
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -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