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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9289921e574adca62309c4dba6ee0f15d257bd39
4
- data.tar.gz: 0a11a1df9dd21d16ee02843ccb9c062cafb88ada
3
+ metadata.gz: d691deea536ba372175b37a2c0172b19b444de75
4
+ data.tar.gz: 6273bb8e9b649e122f162ce7c95df290486cf961
5
5
  SHA512:
6
- metadata.gz: 395c89bc97cacc641a4380f0a28c2ffa06761f4f3ee94a31759c1b9a0e65ea4a6244cdc88686c4d9be615cef839298d226dabe87fd4456257f120ca7800a3e99
7
- data.tar.gz: c35e8428c60ab564b19fdfce9239bac3fbaa8d0e3cbc15887811ecbe5c836977bad42db3cdf43d6981b6af880ff696aff2fc484b10b014f428c716fd7ba46c53
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.
@@ -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).tap do |p|
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).tap do |p|
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 = 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
- interface.primary_key
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
@@ -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
- extra_fields = (fields - data_model.data_fields)
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
@@ -11,9 +11,31 @@ module Mince
11
11
  end
12
12
 
13
13
  module ClassMethods
14
- # Sets or returns the data model class for the model
15
- def find_by_fields(*fields)
16
- raise 'not implemented'
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
@@ -5,7 +5,7 @@ module Mince
5
5
  end
6
6
 
7
7
  def self.minor
8
- 1
8
+ 2
9
9
  end
10
10
 
11
11
  def self.patch
@@ -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