mince 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -1
- data/lib/mince/data_model.rb +56 -13
- data/lib/mince/data_model/timestamps.rb +60 -0
- data/lib/mince/model.rb +1 -1
- data/lib/mince/model/data_model.rb +5 -2
- data/lib/mince/model/finders.rb +25 -3
- data/lib/mince/version.rb +1 -1
- data/spec/integration/inferred_data_fields_spec.rb +62 -0
- data/spec/integration/mince_data_model_spec.rb +52 -0
- data/spec/integration/mince_model_spec.rb +1 -0
- data/spec/support/shared_examples/model_finders_example.rb +139 -8
- data/spec/units/mince/data_model/inferred_fields_spec.rb +79 -0
- data/spec/units/mince/data_model/timestamps_spec.rb +149 -0
- data/spec/units/mince/data_model_spec.rb +116 -23
- data/spec/units/mince/model_spec.rb +107 -66
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d691deea536ba372175b37a2c0172b19b444de75
|
4
|
+
data.tar.gz: 6273bb8e9b649e122f162ce7c95df290486cf961
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b840fbc99a80dc42e94767738877a0fa5fa71b9ae20fbe9bcda8bdb9e33b265dcabe7761c17d9d8180d68b0f8ecceca2f57168a4315743b2dc48b08ece356bb
|
7
|
+
data.tar.gz: 5eb6ee075cc5808d319a4689ba47740f711b82344c37394fa02f641d2eb2555f9055d95a23a02d2990608b27a084e5d151a89c4ba4bfc2d674298f5521ab1861
|
data/README.md
CHANGED
@@ -65,10 +65,11 @@ Link | Description
|
|
65
65
|
----|-----
|
66
66
|
[API Docs](http://rdoc.info/github/coffeencoke/mince/master/frames) | API docs for Mince
|
67
67
|
[Existing Interfaces](https://github.com/coffeencoke/mince/wiki/Existing-interfaces) | List of supported database interfaces that can be used with Mince
|
68
|
+
[Mince Migrator](http://coffeencoke.github.io/mince_migrator) | Database migrations library for Mince
|
68
69
|
[Usage with Rails](https://github.com/coffeencoke/mince/wiki/Usage-with-rails) | More information on how to use Mince with Rails
|
69
70
|
[Fancy Mixins](https://github.com/coffeencoke/mince/wiki/Fancy-mixins) | We've written a few mixins that provide some standard behavior to your models while using Mince
|
70
71
|
[Development](https://github.com/coffeencoke/mince/wiki/Development) | Help by contributing
|
71
|
-
[Mailing List](https://groups.google.com/forum/?fromgroups#!forum/mince_dev)
|
72
|
+
[Mailing List](https://groups.google.com/forum/?fromgroups#!forum/mince_dev) | Mailing list for Mince
|
72
73
|
[Travis CI](https://travis-ci.org/#!/coffeencoke/mince) | Check out the build status of Mince
|
73
74
|
[Why Multitier Architecture?](https://github.com/coffeencoke/mince/wiki/Why-multitier-architecture%3F) | Discussion about why to use multi tier architecture as apposed to others, such as Active Record
|
74
75
|
|
@@ -90,6 +91,12 @@ Helena Converse | [@n3rdgir1](https://twitter.com/n3rdgir1) | [@n3rdgir1](https:
|
|
90
91
|
|
91
92
|
If you've been missed on this list, let us know by creating an issue or sending one of us a message.
|
92
93
|
|
94
|
+
# License
|
95
|
+
|
96
|
+
Copyright 2013 Matt Simpson
|
97
|
+
|
98
|
+
MIT - view LICENSE.txt in the source for details
|
99
|
+
|
93
100
|
# Version 1
|
94
101
|
|
95
102
|
Looking for support for Version 1? Put an issue in, or send one of us a message.
|
data/lib/mince/data_model.rb
CHANGED
@@ -33,6 +33,8 @@ module Mince # :nodoc:
|
|
33
33
|
#
|
34
34
|
# View the docs for each method available
|
35
35
|
module DataModel
|
36
|
+
require_relative 'data_model/timestamps'
|
37
|
+
|
36
38
|
extend ActiveSupport::Concern
|
37
39
|
|
38
40
|
included do
|
@@ -48,6 +50,16 @@ module Mince # :nodoc:
|
|
48
50
|
Config.interface
|
49
51
|
end
|
50
52
|
|
53
|
+
# Allows models to define fields without the data model needing to define them also
|
54
|
+
def infer_fields_from_model
|
55
|
+
@infer_fields_from_model = true
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns true if the data model is infering fields from the model
|
59
|
+
def infer_fields?
|
60
|
+
!!@infer_fields_from_model
|
61
|
+
end
|
62
|
+
|
51
63
|
# Sets what data fields to accept
|
52
64
|
# Returns the fields
|
53
65
|
#
|
@@ -57,6 +69,7 @@ module Mince # :nodoc:
|
|
57
69
|
# @param [*Array] *fields an array of fields to add to the data model
|
58
70
|
# @returns [Array] the fields defined for the data model
|
59
71
|
def data_fields(*fields)
|
72
|
+
@data_fields ||= []
|
60
73
|
create_data_fields(*fields) if fields.any?
|
61
74
|
@data_fields
|
62
75
|
end
|
@@ -76,9 +89,7 @@ module Mince # :nodoc:
|
|
76
89
|
# @param [Class] model a ruby class instance object to store into the data store
|
77
90
|
# @returns [id] returns the id of the newly stored object
|
78
91
|
def store(model)
|
79
|
-
new(model).
|
80
|
-
p.add_to_interface
|
81
|
-
end.id
|
92
|
+
new(model).create
|
82
93
|
end
|
83
94
|
|
84
95
|
# Updates the given model in the data store with the fields and values in model
|
@@ -87,9 +98,7 @@ module Mince # :nodoc:
|
|
87
98
|
#
|
88
99
|
# @param [Class] model a ruby class instance object to store into the data store
|
89
100
|
def update(model)
|
90
|
-
new(model).
|
91
|
-
p.replace_in_interface
|
92
|
-
end
|
101
|
+
new(model).update
|
93
102
|
end
|
94
103
|
|
95
104
|
# Updates a specific field with a value for the record with the given id
|
@@ -99,6 +108,7 @@ module Mince # :nodoc:
|
|
99
108
|
# @param [*] value the value to update the field with
|
100
109
|
def update_field_with_value(id, field, value)
|
101
110
|
interface.update_field_with_value(data_collection, id, field, value)
|
111
|
+
set_update_timestamp_for(data_collection, id) if timestamps?
|
102
112
|
end
|
103
113
|
|
104
114
|
# Increments a field by a given amount
|
@@ -111,6 +121,7 @@ module Mince # :nodoc:
|
|
111
121
|
# @param [Numeric] amount the amount to increment or decrement the field with
|
112
122
|
def increment_field_by_amount(id, field, amount)
|
113
123
|
interface.increment_field_by_amount(data_collection, id, field, amount)
|
124
|
+
set_update_timestamp_for(data_collection, id) if timestamps?
|
114
125
|
end
|
115
126
|
|
116
127
|
# Removes a value from an field that is an array
|
@@ -120,6 +131,7 @@ module Mince # :nodoc:
|
|
120
131
|
# @param [*] value the value to update the field with
|
121
132
|
def remove_from_array(id, field, value)
|
122
133
|
interface.remove_from_array(data_collection, id, field, value)
|
134
|
+
set_update_timestamp_for(data_collection, id) if timestamps?
|
123
135
|
end
|
124
136
|
|
125
137
|
# Pushes a value to an array field
|
@@ -129,6 +141,7 @@ module Mince # :nodoc:
|
|
129
141
|
# @param [*] value the value to update the field with
|
130
142
|
def push_to_array(id, field, value)
|
131
143
|
interface.push_to_array(data_collection, id, field, value)
|
144
|
+
set_update_timestamp_for(data_collection, id) if timestamps?
|
132
145
|
end
|
133
146
|
|
134
147
|
# Returns a record that has the given id
|
@@ -236,9 +249,15 @@ module Mince # :nodoc:
|
|
236
249
|
# @returns [Hash] the data that was added to the data collection, including the uniquely generated id
|
237
250
|
def add(hash)
|
238
251
|
hash = HashWithIndifferentAccess.new(hash.merge(primary_key => generate_unique_id(hash)))
|
252
|
+
add_timestamps_to_hash(hash) if timestamps?
|
239
253
|
interface.add(data_collection, hash).first
|
240
254
|
end
|
241
255
|
|
256
|
+
# Returns true if the data model uses the Timestamps mixin
|
257
|
+
def timestamps?
|
258
|
+
include? Mince::DataModel::Timestamps
|
259
|
+
end
|
260
|
+
|
242
261
|
def translate_from_interface(hash)
|
243
262
|
if hash
|
244
263
|
hash["id"] = hash[primary_key] if hash[primary_key] && (primary_key != :id || primary_key != 'id')
|
@@ -250,19 +269,19 @@ module Mince # :nodoc:
|
|
250
269
|
data.collect {|d| translate_from_interface(d) }
|
251
270
|
end
|
252
271
|
|
253
|
-
private
|
254
|
-
|
255
272
|
def primary_key
|
256
273
|
@primary_key ||= interface.primary_key
|
257
274
|
end
|
258
275
|
|
276
|
+
private
|
277
|
+
|
259
278
|
def set_data_collection(collection_name)
|
260
279
|
@data_collection = collection_name
|
261
280
|
end
|
262
281
|
|
263
282
|
def create_data_fields(*fields)
|
264
283
|
attr_accessor *fields
|
265
|
-
@data_fields
|
284
|
+
@data_fields += fields
|
266
285
|
end
|
267
286
|
end
|
268
287
|
|
@@ -272,14 +291,36 @@ module Mince # :nodoc:
|
|
272
291
|
set_id
|
273
292
|
end
|
274
293
|
|
294
|
+
def create
|
295
|
+
update_timestamps if timestamps?
|
296
|
+
add_to_interface
|
297
|
+
id
|
298
|
+
end
|
299
|
+
|
300
|
+
def update
|
301
|
+
update_timestamps if timestamps?
|
302
|
+
replace_in_interface
|
303
|
+
end
|
304
|
+
|
305
|
+
def timestamps?
|
306
|
+
self.class.timestamps?
|
307
|
+
end
|
308
|
+
|
275
309
|
def interface
|
276
310
|
self.class.interface
|
277
311
|
end
|
278
312
|
|
279
313
|
def data_fields
|
314
|
+
if infer_fields?
|
315
|
+
self.class.data_fields *model.fields
|
316
|
+
end
|
280
317
|
self.class.data_fields
|
281
318
|
end
|
282
319
|
|
320
|
+
def infer_fields?
|
321
|
+
self.class.infer_fields?
|
322
|
+
end
|
323
|
+
|
283
324
|
def data_collection
|
284
325
|
self.class.data_collection
|
285
326
|
end
|
@@ -292,12 +333,14 @@ module Mince # :nodoc:
|
|
292
333
|
interface.replace(data_collection, attributes)
|
293
334
|
end
|
294
335
|
|
295
|
-
private
|
296
|
-
|
297
336
|
def attributes
|
298
|
-
model_instance_values.merge(primary_key => id)
|
337
|
+
model_instance_values.merge(primary_key => id).tap do |hash|
|
338
|
+
hash.merge!(timestamp_attributes) if timestamps?
|
339
|
+
end
|
299
340
|
end
|
300
341
|
|
342
|
+
private
|
343
|
+
|
301
344
|
def model_instance_values
|
302
345
|
HashWithIndifferentAccess.new(model.instance_values).slice(*data_fields)
|
303
346
|
end
|
@@ -319,7 +362,7 @@ module Mince # :nodoc:
|
|
319
362
|
end
|
320
363
|
|
321
364
|
def primary_key
|
322
|
-
|
365
|
+
self.class.primary_key
|
323
366
|
end
|
324
367
|
|
325
368
|
def set_data_field_value(field)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Mince
|
2
|
+
module DataModel
|
3
|
+
require 'active_support/concern'
|
4
|
+
require_relative '../data_model'
|
5
|
+
|
6
|
+
# = Timestamps
|
7
|
+
#
|
8
|
+
# Timestamps can be mixed into your DataModel classes in order to provide with fields
|
9
|
+
# to store when records are created and updated.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
# require 'mince/data_model'
|
13
|
+
#
|
14
|
+
# Class UserDataModel
|
15
|
+
# include Mince::DataModel
|
16
|
+
# include Mince::DataModel::Timestamps
|
17
|
+
#
|
18
|
+
# data_collection :users
|
19
|
+
# data_fields :username
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# UserDataModel.add username: 'coffeencoke'
|
23
|
+
# data_model = UserDataModel.find_by_field :username, 'coffeencoke'
|
24
|
+
# data_model.created_at # => todo: returns date time in utc
|
25
|
+
# data_model.updated_at # => todo: returns date time in utc
|
26
|
+
#
|
27
|
+
# Whenever a database persisting message is called for a record, the updated_at
|
28
|
+
# timestamp will be updated.
|
29
|
+
module Timestamps
|
30
|
+
include Mince::DataModel
|
31
|
+
|
32
|
+
extend ActiveSupport::Concern
|
33
|
+
|
34
|
+
included do
|
35
|
+
data_fields :created_at, :updated_at
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods # :nodoc:
|
39
|
+
def add_timestamps_to_hash(hash)
|
40
|
+
now = Time.now.utc
|
41
|
+
hash.merge!(created_at: now, updated_at: now)
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_update_timestamp_for(data_collection, id)
|
45
|
+
interface.update_field_with_value(data_collection, id, :updated_at, Time.now.utc)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def update_timestamps
|
50
|
+
now = Time.now.utc
|
51
|
+
self.created_at = now unless created_at
|
52
|
+
self.updated_at = now
|
53
|
+
end
|
54
|
+
|
55
|
+
def timestamp_attributes
|
56
|
+
{ created_at: created_at, updated_at: updated_at }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/mince/model.rb
CHANGED
@@ -33,7 +33,7 @@ module Mince
|
|
33
33
|
#
|
34
34
|
# By including this module, you are including DataModel, Fields, Persistence, and Finders.
|
35
35
|
#
|
36
|
-
# However, you can choose which modules you would like by including those modules individually
|
36
|
+
# However, you can choose which modules you would like by including those modules individually
|
37
37
|
# so that you can have flexibility an lighter weight help to implement your models.
|
38
38
|
#
|
39
39
|
# class AvailableBook
|
@@ -21,11 +21,14 @@ module Mince
|
|
21
21
|
# Not sure if this is where this method should live, it requires both
|
22
22
|
# Mince::Model::DataModel and Mince::Model::Fields.
|
23
23
|
def ensure_no_extra_fields
|
24
|
-
|
25
|
-
if extra_fields.any?
|
24
|
+
if !data_model.infer_fields? && extra_fields.any?
|
26
25
|
raise "Tried to save a #{self.class.name} with fields not specified in #{data_model.name}: #{extra_fields.join(', ')}"
|
27
26
|
end
|
28
27
|
end
|
28
|
+
|
29
|
+
def extra_fields
|
30
|
+
@extra_fields ||= (fields - data_model.data_fields)
|
31
|
+
end
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
data/lib/mince/model/finders.rb
CHANGED
@@ -11,9 +11,31 @@ module Mince
|
|
11
11
|
end
|
12
12
|
|
13
13
|
module ClassMethods
|
14
|
-
#
|
15
|
-
def
|
16
|
-
|
14
|
+
# Finds a model for the given field value pair
|
15
|
+
def find_by_field(field, value)
|
16
|
+
d = data_model.find_by_field(field, value)
|
17
|
+
new d if d
|
18
|
+
end
|
19
|
+
|
20
|
+
# Finds a model for the given hash
|
21
|
+
def find_by_fields(hash)
|
22
|
+
d = data_model.find_by_fields(hash)
|
23
|
+
new d if d
|
24
|
+
end
|
25
|
+
|
26
|
+
# Finds all fields that match the given field value pair
|
27
|
+
def all_by_field(field, value)
|
28
|
+
data_model.all_by_field(field, value).map{|a| new(a) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Finds all fields that match the given hash
|
32
|
+
def all_by_fields(hash)
|
33
|
+
data_model.all_by_fields(hash).map{|a| new(a) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Finds or initializes a model for the given hash
|
37
|
+
def find_or_initialize_by(hash)
|
38
|
+
find_by_fields(hash) || new(hash)
|
17
39
|
end
|
18
40
|
|
19
41
|
# Returns all models from the data model
|
data/lib/mince/version.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'debugger'
|
2
|
+
require 'mince'
|
3
|
+
require 'hashy_db'
|
4
|
+
require 'active_support/core_ext/numeric/time'
|
5
|
+
|
6
|
+
describe 'A data model with inferred fields' do
|
7
|
+
subject { model_klass.new attributes }
|
8
|
+
|
9
|
+
let(:attributes) { { brand: brand, price: price, type: type, color: color } }
|
10
|
+
let(:brand) { mock }
|
11
|
+
let(:price) { mock }
|
12
|
+
let(:type) { mock }
|
13
|
+
let(:color) { mock }
|
14
|
+
|
15
|
+
before do
|
16
|
+
Mince::Config.interface = Mince::HashyDb::Interface
|
17
|
+
Mince::HashyDb::Interface.clear
|
18
|
+
end
|
19
|
+
|
20
|
+
after do
|
21
|
+
Mince::HashyDb::Interface.clear
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'a model with a limited set of mince model mixins' do
|
25
|
+
let(:model_klass) do
|
26
|
+
Class.new do
|
27
|
+
include Mince::Model
|
28
|
+
|
29
|
+
data_model(
|
30
|
+
Class.new do
|
31
|
+
include Mince::DataModel
|
32
|
+
|
33
|
+
data_collection :guitars
|
34
|
+
infer_fields_from_model
|
35
|
+
end
|
36
|
+
)
|
37
|
+
field :brand, assignable: true
|
38
|
+
field :price, assignable: true
|
39
|
+
field :type, assignable: true
|
40
|
+
field :color, assignable: true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'is initialized with the correct data' do
|
45
|
+
subject.brand.should == brand
|
46
|
+
subject.price.should == price
|
47
|
+
subject.type.should == type
|
48
|
+
subject.color.should == color
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'persists the data when saved' do
|
52
|
+
subject.save
|
53
|
+
|
54
|
+
all = model_klass.all
|
55
|
+
all.size.should == 1
|
56
|
+
all.first.brand.should == brand
|
57
|
+
all.first.price.should == price
|
58
|
+
all.first.type.should == type
|
59
|
+
all.first.color.should == color
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -39,4 +39,56 @@ describe 'A mince data model integration spec' do
|
|
39
39
|
object[primary_key].should_not be_nil
|
40
40
|
end
|
41
41
|
end
|
42
|
+
|
43
|
+
describe 'a mince data model with timestamps' do
|
44
|
+
subject { data_model_klass.new brand: mock }
|
45
|
+
|
46
|
+
let(:data_model_klass) do
|
47
|
+
Class.new do
|
48
|
+
include Mince::DataModel
|
49
|
+
include Mince::DataModel::Timestamps
|
50
|
+
|
51
|
+
data_collection :guitars
|
52
|
+
data_fields :brand
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'provides a data field for the timestamps' do
|
57
|
+
subject.created_at.should be_nil
|
58
|
+
subject.updated_at.should be_nil
|
59
|
+
subject.data_fields.should =~ [:brand, :updated_at, :created_at]
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when the record is created' do
|
63
|
+
let(:persisted_data_model) { data_model_klass.find_by_field :brand, brand }
|
64
|
+
let(:brand) { 'Gibson' }
|
65
|
+
|
66
|
+
before do
|
67
|
+
data_model_klass.add brand: brand
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'sets the created at timestamp' do
|
71
|
+
(persisted_data_model[:created_at] > 10.seconds.ago.utc && persisted_data_model[:created_at] < 10.seconds.from_now.utc).should be_true
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'sets the updated at timestamp' do
|
75
|
+
(persisted_data_model[:updated_at] > 10.seconds.ago.utc && persisted_data_model[:updated_at] < 10.seconds.from_now.utc).should be_true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when the record is updated' do
|
80
|
+
let(:persisted_data_model) { data_model_klass.find_by_field :brand, brand }
|
81
|
+
let(:brand) { 'Gibson' }
|
82
|
+
|
83
|
+
before do
|
84
|
+
data_model_klass.add brand: brand
|
85
|
+
data_model_klass.update_field_with_value persisted_data_model[:id], :updated_at, (Time.now - 10000).utc
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'updates the updated at timestamp' do
|
89
|
+
persisted_data_model = data_model_klass.find_by_field(:brand, brand)
|
90
|
+
(persisted_data_model[:updated_at] > 10.seconds.ago.utc && persisted_data_model[:updated_at] < 10.seconds.from_now.utc).should be_true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
42
94
|
end
|