mince 2.0.0.pre → 2.0.0.pre.2
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.
- data/lib/mince/config.rb +17 -1
- data/lib/mince/data_model.rb +319 -0
- data/lib/mince/model.rb +161 -0
- data/lib/mince/shared_examples/interface_example.rb +76 -26
- data/lib/mince/version.rb +1 -1
- data/lib/mince.rb +130 -2
- data/spec/integration/simple_mince_data_model_spec.rb +55 -0
- data/spec/units/mince/data_model_spec.rb +228 -0
- data/spec/units/mince/model_spec.rb +128 -0
- metadata +52 -46
data/lib/mince/version.rb
CHANGED
data/lib/mince.rb
CHANGED
@@ -1,2 +1,130 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# = Mince
|
2
|
+
#
|
3
|
+
# Mince is focused to provide support to write database agnostic applications
|
4
|
+
# and to write applications with an architecture that follows the Single
|
5
|
+
# Responsibility principle.
|
6
|
+
#
|
7
|
+
# The highest level difference between using mince and using something
|
8
|
+
# like Rails' ActiveRecord library is that mince does not encourage
|
9
|
+
# the "Active Record Pattern" in general. Look "Active Record Pattern"
|
10
|
+
# up in wikipedia to get a good understanding of what that means.
|
11
|
+
#
|
12
|
+
# Mince encourages separating your data objects and behavior from your
|
13
|
+
# business objects and behavior. These are two separate classes of
|
14
|
+
# objects with two complete separate responsibilities.
|
15
|
+
#
|
16
|
+
# * The data object provides access to the data store for a particular
|
17
|
+
# collection of data.
|
18
|
+
# * The business object adds behavior to the data object in order for the
|
19
|
+
# application to provide business value
|
20
|
+
#
|
21
|
+
# Creating an architecture like this provides more flexibility and more
|
22
|
+
# extensibility for your application. While you are developing your application
|
23
|
+
# you shoud be asking, what is the responsibility of this object? And make clear
|
24
|
+
# separations of behavior and responsibility
|
25
|
+
#
|
26
|
+
# Given that being said. The standard way to structure your directories while
|
27
|
+
# using mince is the following
|
28
|
+
#
|
29
|
+
# ./app
|
30
|
+
# ./models
|
31
|
+
# - authenticator.rb
|
32
|
+
# - user.rb
|
33
|
+
# - project.rb
|
34
|
+
# ./data_models
|
35
|
+
# - user_data_model.rb
|
36
|
+
# - project_data_model.rb
|
37
|
+
#
|
38
|
+
# 1. `Authenticator` is a standard ruby class that uses the user class
|
39
|
+
# to provide strong and clean authentication.
|
40
|
+
# 2. `User` and `Project` are ruby classes with the Mince::Model mixed in to
|
41
|
+
# provide some standard behavior to interact with a mince data model.
|
42
|
+
# 3. `UserDataModel` and `ProjectDataModel` are classes with the Mince::DataModel
|
43
|
+
# mixed in to provide standard behavior to interact with a mince data interface.
|
44
|
+
#
|
45
|
+
# A quick example of the extensibility of this would be if you needed to get users
|
46
|
+
# from LDAP or Active Directory rather than from MongoDB. This is a pretty common
|
47
|
+
# request and similar to other requirements for enterprise applications.
|
48
|
+
#
|
49
|
+
# With this architecture you could easily expand it to the following:
|
50
|
+
#
|
51
|
+
# ./app
|
52
|
+
# ./models
|
53
|
+
# - authenticator.rb
|
54
|
+
# - user.rb
|
55
|
+
# - project.rb
|
56
|
+
# ./data_models
|
57
|
+
# - ldap_user_data_model.rb
|
58
|
+
# - project_data_model.rb
|
59
|
+
#
|
60
|
+
# The only difference is now, instead of using a mince user data model, you are using
|
61
|
+
# a custom ldap user data model. As long as the API you wrote for the ldap user data
|
62
|
+
# model requires the same input and provides the same output as the mince user data
|
63
|
+
# model, then you did not have to change any other code in your application.
|
64
|
+
#
|
65
|
+
# Let's take this a little further. Say you have LDAP auth now, and you've continued
|
66
|
+
# developing on your application. You start feeling some pain with having your
|
67
|
+
# development environment depend on so many external systems in order to be up and
|
68
|
+
# running. Think about it, right now you are dependent on MongoDB and a deve LDAP
|
69
|
+
# environment to be running and seeded with data. That was the position I was in
|
70
|
+
# and one of the reasons I wrote mince.
|
71
|
+
#
|
72
|
+
# Let's extend the architecture like so:
|
73
|
+
#
|
74
|
+
# - app/
|
75
|
+
# - models/
|
76
|
+
# - authenticator.rb
|
77
|
+
# - user.rb
|
78
|
+
# - user_data_provider.rb
|
79
|
+
# - project.rb
|
80
|
+
# ./data_models
|
81
|
+
# - ldap_user_data_model.rb
|
82
|
+
# - user_data_model.rb
|
83
|
+
# - project_data_model.rb
|
84
|
+
#
|
85
|
+
# I added a user data model and user data provider. The user data provider determines
|
86
|
+
# which user data source to use for the specific environment that you are in.
|
87
|
+
#
|
88
|
+
# If you are running in a development environment, then you would configure it to
|
89
|
+
# use the user data model, if you are in staging, or production environments, you
|
90
|
+
# would use the ldap user data model.
|
91
|
+
#
|
92
|
+
# This removes one external system dependency from our development environment, let's
|
93
|
+
# remove MongoDB from being a development dependency as well so that we can focus
|
94
|
+
# on the business logic of our application, and not waste time on database maintenance
|
95
|
+
# issues.
|
96
|
+
#
|
97
|
+
# Configure mince to use hashy_db when you are running in Development mode like so:
|
98
|
+
# # require mince, if you haven't already
|
99
|
+
# require 'mince'
|
100
|
+
#
|
101
|
+
# # require the gem (needs to be installed via `gem install`, bundler, gemspec,
|
102
|
+
# # or some other way)
|
103
|
+
# require 'hashy_db'
|
104
|
+
#
|
105
|
+
# # Tell mince to use hashy_db
|
106
|
+
# Mince::Config.interface = Mince::HashyDb::Interface
|
107
|
+
#
|
108
|
+
# Now, all of mince related data interactions will use an in-memory hash.
|
109
|
+
#
|
110
|
+
# Configure mince to use mingo (a mince MongoDB interface) when you are running in Staging, or Production mode:
|
111
|
+
# # require mince, if you haven't already
|
112
|
+
# require 'mince'
|
113
|
+
#
|
114
|
+
# # require the gem (needs to be installed via `gem install`, bundler, gemspec,
|
115
|
+
# # or some other way)
|
116
|
+
# require 'mingo'
|
117
|
+
#
|
118
|
+
# # Tell mince to use mingo
|
119
|
+
# Mince::Config.interface = Mince::Mingo::Interface
|
120
|
+
#
|
121
|
+
# Done. Now we can simply start the ruby server and develop away.
|
122
|
+
#
|
123
|
+
# @author Matt Simpson
|
124
|
+
module Mince
|
125
|
+
# Load all mince libraries
|
126
|
+
require_relative 'mince/version'
|
127
|
+
require_relative 'mince/config'
|
128
|
+
require_relative 'mince/data_model'
|
129
|
+
require_relative 'mince/model'
|
130
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'mince'
|
2
|
+
require 'hashy_db'
|
3
|
+
|
4
|
+
describe 'A simple mince data model integration spec' do
|
5
|
+
before do
|
6
|
+
Mince::Config.interface = Mince::HashyDb::Interface
|
7
|
+
Mince::HashyDb::Interface.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Mince::HashyDb::Interface.clear
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'a model' do
|
15
|
+
subject { model_klass.new attributes }
|
16
|
+
|
17
|
+
let(:model_klass) do
|
18
|
+
Class.new do
|
19
|
+
include Mince::Model
|
20
|
+
|
21
|
+
data_model(
|
22
|
+
Class.new do
|
23
|
+
include Mince::DataModel
|
24
|
+
|
25
|
+
data_collection :guitars
|
26
|
+
data_fields :brand, :price, :type, :color
|
27
|
+
end
|
28
|
+
)
|
29
|
+
fields :brand, :price, :type, :color
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
let(:attributes) { { brand: brand, price: price, type: type, color: color } }
|
34
|
+
let(:brand) { mock }
|
35
|
+
let(:price) { mock }
|
36
|
+
let(:type) { mock }
|
37
|
+
let(:color) { mock }
|
38
|
+
|
39
|
+
it 'is initialized with the correct data' do
|
40
|
+
subject.brand.should == brand
|
41
|
+
subject.price.should == price
|
42
|
+
subject.type.should == type
|
43
|
+
subject.color.should == color
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can be persisted to the mince data interface' do
|
47
|
+
subject.save
|
48
|
+
|
49
|
+
raw_record = Mince::Config.interface.find(:guitars, Mince::HashyDb::Interface.primary_key, subject.id)
|
50
|
+
model_record = model_klass.find(subject.id)
|
51
|
+
raw_record[:brand].should == brand
|
52
|
+
model_record.brand.should == brand
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require_relative '../../../lib/mince/data_model'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
describe Mince::DataModel, 'Mixin' do
|
5
|
+
let(:described_class) { klass }
|
6
|
+
|
7
|
+
let(:collection_name) { :guitars }
|
8
|
+
let(:data_field_attributes) do
|
9
|
+
{
|
10
|
+
brand: 'a brand everyone knows',
|
11
|
+
price: 'a price you save up for',
|
12
|
+
type: 'the kind you want',
|
13
|
+
color: 'should be your favorite'
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:klass) do
|
18
|
+
Class.new do
|
19
|
+
include Mince::DataModel
|
20
|
+
|
21
|
+
data_collection :guitars
|
22
|
+
data_fields :brand, :price, :type, :color
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:interface) { mock 'mince data interface class', generate_unique_id: unique_id, primary_key: primary_key }
|
27
|
+
let(:unique_id) { mock 'id' }
|
28
|
+
let(:primary_key) { "custom_id" }
|
29
|
+
|
30
|
+
before do
|
31
|
+
Mince::Config.stub(:interface => interface)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "storing a data model" do
|
35
|
+
let(:model) { mock 'a model', instance_values: data_field_attributes }
|
36
|
+
|
37
|
+
before do
|
38
|
+
interface.stub(:add)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'generates a unique id using the model as a salt' do
|
42
|
+
interface.should_receive(:generate_unique_id).with(model).and_return(unique_id)
|
43
|
+
|
44
|
+
described_class.store(model)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'adds the data model to the db store' do
|
48
|
+
interface.should_receive(:add).with(collection_name, HashWithIndifferentAccess.new({primary_key => unique_id}).merge(data_field_attributes))
|
49
|
+
|
50
|
+
described_class.store(model)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'can delete the collection' do
|
55
|
+
interface.should_receive(:delete_collection).with(collection_name)
|
56
|
+
|
57
|
+
described_class.delete_collection
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'can delete a field' do
|
61
|
+
field = mock 'field to delete from the collection'
|
62
|
+
|
63
|
+
interface.should_receive(:delete_field).with(collection_name, field)
|
64
|
+
|
65
|
+
described_class.delete_field(field)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'can delete records by a given set of params' do
|
69
|
+
params = mock 'params that provide a condition of what records to delete from the collection'
|
70
|
+
|
71
|
+
interface.should_receive(:delete_by_params).with(collection_name, params)
|
72
|
+
|
73
|
+
described_class.delete_by_params(params)
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "updating a data model" do
|
77
|
+
let(:data_model_id) { '1234567' }
|
78
|
+
let(:model) { mock 'a model', id: data_model_id, instance_values: data_field_attributes }
|
79
|
+
|
80
|
+
before do
|
81
|
+
interface.stub(:replace)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'replaces the data model in the db store' do
|
85
|
+
interface.should_receive(:replace).with(collection_name, HashWithIndifferentAccess.new({primary_key => data_model_id}).merge(data_field_attributes))
|
86
|
+
|
87
|
+
described_class.update(model)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'updating a specific field for a data model' do
|
92
|
+
let(:data_model_id) { '1234567' }
|
93
|
+
|
94
|
+
it 'has the data store update the field' do
|
95
|
+
interface.should_receive(:update_field_with_value).with(collection_name, data_model_id, :some_field, 'some value')
|
96
|
+
|
97
|
+
described_class.update_field_with_value(data_model_id, :some_field, 'some value')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'incrementing a specific field by a given an amount' do
|
102
|
+
let(:data_model_id) { mock 'id' }
|
103
|
+
|
104
|
+
it 'has the data store update the field' do
|
105
|
+
interface.should_receive(:increment_field_by_amount).with(collection_name, data_model_id, :some_field, 4)
|
106
|
+
|
107
|
+
described_class.increment_field_by_amount(data_model_id, :some_field, 4)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "pushing a value to an array for a data model" do
|
112
|
+
let(:data_model_id) { '1234567' }
|
113
|
+
|
114
|
+
it 'replaces the data model in the db store' do
|
115
|
+
interface.should_receive(:push_to_array).with(collection_name, primary_key, data_model_id, :array_field, 'some value')
|
116
|
+
|
117
|
+
described_class.push_to_array(data_model_id, :array_field, 'some value')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "getting all data models with a specific value for a field" do
|
122
|
+
let(:data_model) { {primary_key => 'some id'} }
|
123
|
+
let(:expected_data_models) { [HashWithIndifferentAccess.new({:id => 'some id', primary_key => 'some id'})] }
|
124
|
+
let(:data_models) { [data_model] }
|
125
|
+
subject { described_class.array_contains(:some_field, 'some value') }
|
126
|
+
|
127
|
+
it 'returns the stored data models with the requested field / value' do
|
128
|
+
interface.should_receive(:array_contains).with(collection_name, :some_field, 'some value').and_return(data_models)
|
129
|
+
|
130
|
+
subject.should == expected_data_models
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "removing a value from an array for a data model" do
|
135
|
+
let(:data_model_id) { '1234567' }
|
136
|
+
|
137
|
+
it 'removes the value from the array' do
|
138
|
+
interface.should_receive(:remove_from_array).with(collection_name, primary_key, data_model_id, :array_field, 'some value')
|
139
|
+
|
140
|
+
described_class.remove_from_array(data_model_id, :array_field, 'some value')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'getting all of the data models' do
|
145
|
+
let(:data_model) { {primary_key => 'some id'} }
|
146
|
+
let(:expected_data_models) { [HashWithIndifferentAccess.new({:id => 'some id', primary_key => 'some id'})] }
|
147
|
+
let(:data_models) { [data_model] }
|
148
|
+
|
149
|
+
it 'returns the stored data models' do
|
150
|
+
interface.should_receive(:find_all).with(collection_name).and_return(data_models)
|
151
|
+
|
152
|
+
described_class.all.should == expected_data_models
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "getting all the data fields by a parameter hash" do
|
157
|
+
let(:data_model) { {primary_key => 'some id'} }
|
158
|
+
let(:expected_data_models) { [{:id => 'some id', primary_key => 'some id'}] }
|
159
|
+
let(:data_models) { [data_model] }
|
160
|
+
let(:expected_data_models) { [HashWithIndifferentAccess.new(data_model)] }
|
161
|
+
|
162
|
+
it 'passes the hash to the interface_class' do
|
163
|
+
interface.should_receive(:get_all_for_key_with_value).with(collection_name, :field2, 'not nil').and_return(data_models)
|
164
|
+
|
165
|
+
described_class.all_by_field(:field2, 'not nil').should == expected_data_models
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "getting a record by a set of key values" do
|
170
|
+
let(:data_model) { {primary_key => 'some id'} }
|
171
|
+
let(:data_models) { [data_model] }
|
172
|
+
let(:expected_data_models) { [{:id => 'some id', primary_key => 'some id'}] }
|
173
|
+
|
174
|
+
let(:sample_hash) { {field1: nil, field2: 'not nil'} }
|
175
|
+
|
176
|
+
it 'passes the hash to the interface_class' do
|
177
|
+
interface.should_receive(:get_by_params).with(collection_name, sample_hash).and_return(data_models)
|
178
|
+
|
179
|
+
described_class.find_by_fields(sample_hash).should == HashWithIndifferentAccess.new(expected_data_models.first)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "getting all of the data models for a where a field contains any value of a given array of values" do
|
184
|
+
let(:data_models) { [{primary_key => 'some id'}, {primary_key => 'some id 2'}] }
|
185
|
+
let(:expected_data_models) { [{"id" => 'some id', primary_key => 'some id'}, {"id" => 'some id 2', primary_key => 'some id 2'}] }
|
186
|
+
|
187
|
+
subject { described_class.containing_any(:some_field, ['value 1', 'value 2']) }
|
188
|
+
|
189
|
+
it 'returns the stored data models' do
|
190
|
+
interface.should_receive(:containing_any).with(collection_name, :some_field, ['value 1', 'value 2']).and_return(data_models)
|
191
|
+
|
192
|
+
subject.should == expected_data_models
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe "getting a record by a key and value" do
|
197
|
+
let(:data_model) { {primary_key => 'some id'} }
|
198
|
+
let(:expected_data_model) { {:id => 'some id', primary_key => 'some id'} }
|
199
|
+
|
200
|
+
it 'returns the correct data model' do
|
201
|
+
interface.should_receive(:get_for_key_with_value).with(collection_name, :field2, 'not nil').and_return(data_model)
|
202
|
+
|
203
|
+
described_class.find_by_field(:field2, 'not nil').should == HashWithIndifferentAccess.new(expected_data_model)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "getting all data models with a specific value for a field" do
|
208
|
+
let(:data_models) { [{primary_key => 'some id'}, {primary_key => 'some id 2'}] }
|
209
|
+
let(:expected_data_models) { [HashWithIndifferentAccess.new({:id => 'some id', primary_key => 'some id'}), HashWithIndifferentAccess.new({id: 'some id 2', primary_key => 'some id 2'})] }
|
210
|
+
subject { described_class.all_by_field(:some_field, 'some value') }
|
211
|
+
|
212
|
+
it 'returns the stored data models with the requested field / value' do
|
213
|
+
interface.should_receive(:get_all_for_key_with_value).with(collection_name, :some_field, 'some value').and_return(data_models)
|
214
|
+
|
215
|
+
subject.should == expected_data_models
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe 'getting a specific data model' do
|
220
|
+
let(:data_model) { {primary_key => 'id', :id => 'id' } }
|
221
|
+
|
222
|
+
it 'returns the data model from the data store' do
|
223
|
+
interface.should_receive(:find).with(collection_name, primary_key, 'id').and_return(data_model)
|
224
|
+
|
225
|
+
described_class.find(data_model[:id]).should == HashWithIndifferentAccess.new(data_model)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require_relative '../../../lib/mince/model'
|
2
|
+
|
3
|
+
describe Mince::Model do
|
4
|
+
let(:klass) do
|
5
|
+
Class.new do
|
6
|
+
include Mince::Model
|
7
|
+
|
8
|
+
data_model Class.new
|
9
|
+
|
10
|
+
field :meh
|
11
|
+
field :foo, assignable: true
|
12
|
+
field :bar, assignable: false
|
13
|
+
fields :baz, :qaaz
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:meh) { mock 'meh' }
|
18
|
+
let(:foo) { mock 'foo' }
|
19
|
+
let(:bar) { mock 'bar' }
|
20
|
+
let(:baz) { mock 'baz' }
|
21
|
+
let(:qaaz) { mock 'qaaz' }
|
22
|
+
|
23
|
+
subject { klass.new(meh: meh, foo: foo, bar: bar, baz: baz, qaaz: qaaz) }
|
24
|
+
|
25
|
+
it 'initializes the object and assigns values to the fields' do
|
26
|
+
subject.meh.should == meh
|
27
|
+
subject.foo.should == foo
|
28
|
+
subject.bar.should == bar
|
29
|
+
subject.baz.should == baz
|
30
|
+
subject.qaaz.should == qaaz
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'can set the assignable fields outside of the initilizer' do
|
34
|
+
subject.foo = 'foo1'
|
35
|
+
subject.foo.should == 'foo1'
|
36
|
+
subject.attributes = { foo: 'foo2' }
|
37
|
+
subject.foo.should == 'foo2'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'cannot set the readonly field outside of the initilizer' do
|
41
|
+
subject.attributes = { bar: 'bar1' }
|
42
|
+
subject.bar.should == bar
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'fields are readonly by default' do
|
46
|
+
subject.attributes = { meh: 'meh1' }
|
47
|
+
subject.meh.should == meh
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'saving' do
|
51
|
+
let(:id) { mock 'id' }
|
52
|
+
let(:data_fields) { subject.fields }
|
53
|
+
|
54
|
+
before do
|
55
|
+
subject.data_model.stub(:data_fields).and_return(data_fields)
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'when the model has fields that are not defined in the data model' do
|
59
|
+
let(:data_fields) { subject.fields - extra_fields }
|
60
|
+
let(:extra_fields) { subject.fields[0..-2] }
|
61
|
+
|
62
|
+
it 'raises an exception with a message' do
|
63
|
+
expect { subject.save }.to raise_error("Tried to save a #{subject.class.name} with fields not specified in #{subject.data_model.name}: #{extra_fields.join(', ')}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when it has not yet been persisted to the mince data model' do
|
68
|
+
before do
|
69
|
+
subject.data_model.stub(:store => id)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'stores the model' do
|
73
|
+
subject.data_model.should_receive(:store).with(subject).and_return(id)
|
74
|
+
|
75
|
+
subject.save
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'assigns the id' do
|
79
|
+
subject.save
|
80
|
+
|
81
|
+
subject.id.should == id
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when it has already been persisted to the mince data model' do
|
86
|
+
let(:subject) { klass.new(id: id) }
|
87
|
+
|
88
|
+
it 'updates the model' do
|
89
|
+
subject.data_model.should_receive(:update).with(subject)
|
90
|
+
|
91
|
+
subject.save
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "Query Methods:" do
|
97
|
+
describe 'finding a model by id' do
|
98
|
+
subject { klass.find(id) }
|
99
|
+
|
100
|
+
let(:id) { mock 'id' }
|
101
|
+
|
102
|
+
before do
|
103
|
+
klass.data_model.stub(:find).with(id).and_return(data)
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when it exists' do
|
107
|
+
let(:data) { mock 'data' }
|
108
|
+
let(:model) { mock 'model' }
|
109
|
+
|
110
|
+
before do
|
111
|
+
klass.stub(:new).with(data).and_return(model)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'returns the model' do
|
115
|
+
subject.should == model
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'when it does not exist' do
|
120
|
+
let(:data) { nil }
|
121
|
+
|
122
|
+
it 'returns nothing' do
|
123
|
+
subject.should be_nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|