mince 2.1.0 → 2.2.0

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.
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