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 CHANGED
@@ -1,15 +1,31 @@
1
- module Mince
1
+ module Mince # :nodoc:
2
2
  require 'singleton'
3
3
 
4
+ # = Configuration for Mince
5
+ #
6
+ # Mince can be configured to interact with different mince supported data interfaces.
7
+ #
8
+ # Simply run the following to specify the data interface to use
9
+ # Mince::Config.interface = Mince::MyDb::Interface
10
+ #
11
+ # This is a singleton object in order to prevent multiple instances of this object from
12
+ # being used.
4
13
  class Config
5
14
  include Singleton
6
15
 
7
16
  attr_accessor :interface
8
17
 
18
+ # Sets the singleton's interface attribute so that you can change your storage strategy
19
+ # without changing all references to that class.
20
+ #
21
+ # @param [Class] interface the Mince Supported Interface class
9
22
  def self.interface=(interface)
10
23
  instance.interface = interface
11
24
  end
12
25
 
26
+ # Returns the interface that is configured to be used. Use this method instead of hard coding
27
+ # which mince interface to use throughout your code so that you can change mince interfaces
28
+ # as needed.
13
29
  def self.interface
14
30
  instance.interface
15
31
  end
@@ -0,0 +1,319 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+ require 'active_support/core_ext/object/instance_variables'
3
+ require 'active_support/core_ext/hash/slice'
4
+ require 'active_support/concern'
5
+
6
+ require_relative 'config'
7
+
8
+ module Mince # :nodoc:
9
+ # = DataModel
10
+ #
11
+ # Mince::DataModel is used as a mixin to easily mixin behavior into an object that
12
+ # you wish to act as a data model and interact with mince data interfaces.
13
+ #
14
+ # Simply mixin this module in order to get a wrapper class to interact with a mince
15
+ # interface for a specific collection
16
+ #
17
+ # Example:
18
+ # require 'mince/data_model'
19
+ #
20
+ # class UserDataModel
21
+ # include Mince::DataModel
22
+ #
23
+ # data_collection :users
24
+ # data_fields :username, :first_name, :last_name, :email, :password
25
+ # end
26
+ #
27
+ # To access methods on the data model do the following:
28
+ # user = User.new first_name: 'Matt' # initialize a user from some User class
29
+ # user.id = UserDataModel.store user # returns the id of the stored user
30
+ #
31
+ # UserDataModel.find 1 # => returns the data for the user with an id of 1
32
+ # UserDataModel.find_all # => returns all users
33
+ #
34
+ # View the docs for each method available
35
+ module DataModel
36
+ extend ActiveSupport::Concern
37
+
38
+ included do
39
+ # creates readonly attribute for id and model
40
+ attr_reader :id, :model
41
+ end
42
+
43
+ module ClassMethods # :nodoc:
44
+ # The interface configured by Mince::Config.interface=
45
+ #
46
+ # @returns [Class] interface class to be used by all mince data interactions
47
+ def interface
48
+ Config.interface
49
+ end
50
+
51
+ # Sets what data fields to accept
52
+ # Returns the fields
53
+ #
54
+ # * Calling multiple times will do an ammendment to the existing list of fields
55
+ # * Calling without any fields will simply return the current list of fields
56
+ #
57
+ # @param [*Array] *fields an array of fields to add to the data model
58
+ # @returns [Array] the fields defined for the data model
59
+ def data_fields(*fields)
60
+ create_data_fields(*fields) if fields.any?
61
+ @data_fields
62
+ end
63
+
64
+ # Sets the name of the data collection
65
+ # Returns the name of the data collection
66
+ #
67
+ # @param [String] collection_name the name of the collection to use
68
+ # @returns [String] returns the name of the collection
69
+ def data_collection(collection_name = nil)
70
+ set_data_collection(collection_name) if collection_name
71
+ @data_collection
72
+ end
73
+
74
+ # Stores the given model into the data store
75
+ #
76
+ # @param [Class] model a ruby class instance object to store into the data store
77
+ # @returns [id] returns the id of the newly stored object
78
+ def store(model)
79
+ new(model).tap do |p|
80
+ p.add_to_interface
81
+ end.id
82
+ end
83
+
84
+ # Updates the given model in the data store with the fields and values in model
85
+ #
86
+ # Uses model.id to find the record to update
87
+ #
88
+ # @param [Class] model a ruby class instance object to store into the data store
89
+ def update(model)
90
+ new(model).tap do |p|
91
+ p.replace_in_interface
92
+ end
93
+ end
94
+
95
+ # Updates a specific field with a value for the record with the given id
96
+ #
97
+ # @param [id] id the id of the record to update
98
+ # @param [Symbol] field the field to update
99
+ # @param [*] value the value to update the field with
100
+ def update_field_with_value(id, field, value)
101
+ interface.update_field_with_value(data_collection, id, field, value)
102
+ end
103
+
104
+ # Increments a field by a given amount
105
+ #
106
+ # Some databases provide very efficient algorithms for incrementing or decrementing a
107
+ # value.
108
+ #
109
+ # @param [id] id the id of the record to update
110
+ # @param [Symbol] field the field to update
111
+ # @param [Numeric] amount the amount to increment or decrement the field with
112
+ def increment_field_by_amount(id, field, amount)
113
+ interface.increment_field_by_amount(data_collection, id, field, amount)
114
+ end
115
+
116
+ # Removes a value from an field that is an array
117
+ #
118
+ # @param [id] id the id of the record to update
119
+ # @param [Symbol] field the field to update
120
+ # @param [*] value the value to update the field with
121
+ def remove_from_array(id, field, value)
122
+ interface.remove_from_array(data_collection, interface.primary_key, id, field, value)
123
+ end
124
+
125
+ # Pushes a value to an array field
126
+ #
127
+ # @param [id] id the id of the record to update
128
+ # @param [Symbol] field the field to update
129
+ # @param [*] value the value to update the field with
130
+ def push_to_array(id, field, value)
131
+ interface.push_to_array(data_collection, interface.primary_key, id, field, value)
132
+ end
133
+
134
+ # Returns a record that has the given id
135
+ #
136
+ # Returns nil if nothing found
137
+ #
138
+ # @param [id] id the id of the record to find
139
+ # @returns [HashWithIndifferentAccess, nil] a hash with the data for the record
140
+ def find(id)
141
+ translate_from_interface interface.find(data_collection, interface.primary_key, id)
142
+ end
143
+
144
+ # Deletes a field from all records in the collection
145
+ #
146
+ # @param [Symbol] field the field to remove
147
+ def delete_field(field)
148
+ interface.delete_field(data_collection, field)
149
+ end
150
+
151
+
152
+ # Deletes a field from all records that matches the field / value key pairs in the
153
+ # params provided in the collection
154
+ #
155
+ # This will only delete records that match all key/value pairs in the params hash.
156
+ #
157
+ # DataModel.delete_by_params(username: 'railsgrammer', first_name: 'Matt Simpson')
158
+ #
159
+ # @param [Hash] params the key/value pair hash to delete records by
160
+ def delete_by_params(params)
161
+ interface.delete_by_params(data_collection, params)
162
+ end
163
+
164
+ # Finds all records in the collection
165
+ #
166
+ # @returns [Array] all records in the collection, empty array if non found.
167
+ def all
168
+ translate_each_from_interface interface.find_all(data_collection)
169
+ end
170
+
171
+ # Returns all records in the collection that has the given field and value
172
+ #
173
+ # @param [Symbol] field the field to query for
174
+ # @param [*] value the value to query on the field for
175
+ # @returns [Array] the set of records matching the field / value pair
176
+ def all_by_field(field, value)
177
+ translate_each_from_interface interface.get_all_for_key_with_value(data_collection, field, value)
178
+ end
179
+
180
+ # Finds all recurds that match a set of key / value pairs
181
+ #
182
+ # @param [Hash] hash a hash of field / value pairs to query records for
183
+ # @returns [Array] the set of records matching all key / value pairs
184
+ def all_by_fields(hash)
185
+ translate_each_from_interface interface.get_by_params(data_collection, hash)
186
+ end
187
+
188
+ # Finds One record that matches all of the field / value pairs
189
+ #
190
+ # @param [Hash] hash the hash to query for
191
+ # @returns [Hash] a hash containing the data for the found record, nil is returned when nothing is found
192
+ def find_by_fields(hash)
193
+ translate_from_interface all_by_fields(hash).first
194
+ end
195
+
196
+ # Finds One record that matches a field / value pair
197
+ #
198
+ # @param [Symbol] field the field to query for
199
+ # @param [*] value the value to query on the field for
200
+ # @returns [Hash] a hash containing the data for the found record, nil is returned when nothing is found
201
+ def find_by_field(field, value)
202
+ translate_from_interface interface.get_for_key_with_value(data_collection, field, value)
203
+ end
204
+
205
+ # Finds all records where the field contains any of the values
206
+ #
207
+ # @param [Symbol] field the field to query against
208
+ # @param [Array] values an array of values to get records for
209
+ # @returns [Array] an array containing all records that match the criteria
210
+ def containing_any(field, values)
211
+ translate_each_from_interface interface.containing_any(data_collection, field, values)
212
+ end
213
+
214
+ # Finds all records where the field, which must be an array field, contains the value
215
+ #
216
+ # @param [Symbol] field the field to query against
217
+ # @param [*] value the value to query against
218
+ # @returns [Array] an array containing all records that match the criteria
219
+ def array_contains(field, value)
220
+ translate_each_from_interface interface.array_contains(data_collection, field, value)
221
+ end
222
+
223
+ # Deletes the entire collection from the database
224
+ def delete_collection
225
+ interface.delete_collection(data_collection)
226
+ end
227
+
228
+ private
229
+
230
+ def translate_from_interface(hash)
231
+ if hash
232
+ hash["id"] = hash[primary_key] if hash[primary_key]
233
+ HashWithIndifferentAccess.new hash
234
+ end
235
+ end
236
+
237
+ def translate_each_from_interface(data)
238
+ data.collect {|d| translate_from_interface(d) }
239
+ end
240
+
241
+ def primary_key
242
+ @primary_key ||= interface.primary_key
243
+ end
244
+
245
+ def set_data_collection(collection_name)
246
+ @data_collection = collection_name
247
+ end
248
+
249
+ def create_data_fields(*fields)
250
+ attr_accessor *fields
251
+ @data_fields = fields
252
+ end
253
+ end
254
+
255
+ def initialize(model)
256
+ @model = model
257
+ set_data_field_values
258
+ set_id
259
+ end
260
+
261
+ def interface
262
+ self.class.interface
263
+ end
264
+
265
+ def data_fields
266
+ self.class.data_fields
267
+ end
268
+
269
+ def data_collection
270
+ self.class.data_collection
271
+ end
272
+
273
+ def add_to_interface
274
+ interface.add(data_collection, attributes)
275
+ end
276
+
277
+ def replace_in_interface
278
+ interface.replace(data_collection, attributes)
279
+ end
280
+
281
+ private
282
+
283
+ def attributes
284
+ model_instance_values.merge(primary_key => id)
285
+ end
286
+
287
+ def model_instance_values
288
+ HashWithIndifferentAccess.new(model.instance_values).slice(*data_fields)
289
+ end
290
+
291
+ def set_id
292
+ @id = model_has_id? ? model.id : generated_id
293
+ end
294
+
295
+ def generated_id
296
+ interface.generate_unique_id(model)
297
+ end
298
+
299
+ def model_has_id?
300
+ model.respond_to?(:id) && model.id
301
+ end
302
+
303
+ def set_data_field_values
304
+ data_fields.each { |field| set_data_field_value(field) }
305
+ end
306
+
307
+ def primary_key
308
+ interface.primary_key
309
+ end
310
+
311
+ def set_data_field_value(field)
312
+ self.send("#{field}=", model.send(field)) if field_exists?(field)
313
+ end
314
+
315
+ def field_exists?(field)
316
+ model.respond_to?(field) && !model.send(field).nil?
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,161 @@
1
+ require 'active_support'
2
+ require 'active_model'
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'active_support/core_ext/object/instance_variables'
5
+
6
+ module Mince
7
+ # = Model
8
+ #
9
+ # The mince model is a module that provides standard model to data behavior to the Mince data model mixing for a specific model.
10
+ #
11
+ # Simply mixin this module in order to get a wrapper class to interact with a mince
12
+ # interface for a specific collection
13
+ #
14
+ # Example:
15
+ # require 'mince'
16
+ #
17
+ # class BookDataModel
18
+ # include Mince::DataModel
19
+ #
20
+ # data_collection :books
21
+ # data_fields :title, :publisher
22
+ # end
23
+ #
24
+ # class Book
25
+ # include Mince::Model
26
+ #
27
+ # data_model BookDataModel
28
+ # fields :title, :publisher
29
+ # end
30
+ #
31
+ # book = Book.new title: 'The World In Photographs', publisher: 'National Geographic'
32
+ # book.save
33
+ #
34
+ # View the docs for each method available
35
+ module Model
36
+ extend ActiveSupport::Concern
37
+
38
+ included do
39
+ include ActiveModel::Conversion
40
+ extend ActiveModel::Naming
41
+
42
+ attr_accessor :id
43
+ end
44
+
45
+ module ClassMethods
46
+ # Sets or returns the data model class for the model
47
+ def data_model(model=nil)
48
+ @data_model = model if model
49
+ @data_model
50
+ end
51
+
52
+ # Returns all models from the data model
53
+ def all
54
+ data_model.all.map{|a| new a }
55
+ end
56
+
57
+ # Returns a model that matches a given id, returns nil if none found
58
+ def find(id)
59
+ a = data_model.find(id)
60
+ new a if a
61
+ end
62
+
63
+ # Adds a field to the object. Takes options to indicate assignability. If `assignable`
64
+ # is set, the field will be assignable via
65
+ # model.field = 'foo'
66
+ # model.attributes = { field: 'foo' }
67
+ #
68
+ def field(field_name, options={})
69
+ if options[:assignable]
70
+ add_assignable_field(field_name)
71
+ else
72
+ add_readonly_field(field_name)
73
+ end
74
+ end
75
+
76
+ # Adds a read only field, values for these fields can only be set in the class itself
77
+ # or from the hash sent in to the initializer
78
+ def add_readonly_field(field_name)
79
+ fields << field_name
80
+ readonly_fields << field_name
81
+ attr_reader field_name
82
+ end
83
+
84
+ # Adds an assignable field, values for these fields can be set using the field writer
85
+ # or from the `attributes=` method.
86
+ def add_assignable_field(field_name)
87
+ fields << field_name
88
+ assignable_fields << field_name
89
+ attr_accessor field_name
90
+ end
91
+
92
+ # Adds multiple readonly fields to the fields array, and returns the current list
93
+ # of fields. If no fields are given, it just returns the current list of fields
94
+ def fields(*field_names)
95
+ @fields ||= []
96
+ field_names.each {|field_name| field(field_name) }
97
+ @fields
98
+ end
99
+
100
+ # Returns the list of assignable fields
101
+ def assignable_fields
102
+ @assignable_fields ||= []
103
+ end
104
+
105
+ # Returns the list of readonly fields
106
+ def readonly_fields
107
+ @readonly_fields ||= []
108
+ end
109
+ end
110
+
111
+ delegate :data_model, :assignable_fields, :readonly_fields, :fields, to: 'self.class'
112
+
113
+ # Sets values (for fields defined by calling .field or .fields) in the hash to the object
114
+ # includes assignable and non-assignable fields
115
+ def initialize(hash={})
116
+ @id = hash[:id]
117
+ readonly_fields.each do |field_name|
118
+ self.instance_variable_set("@#{field_name}", hash[field_name]) if hash[field_name]
119
+ end
120
+ self.attributes = hash
121
+ end
122
+
123
+ # Returns true if the record indicates that it has been persisted to a data model.
124
+ # Returns false otherwise.
125
+ def persisted?
126
+ !!id
127
+ end
128
+
129
+ # Saves the object to the data model. Stores if new, updates previous entry if it has already
130
+ # been saved.
131
+ def save
132
+ ensure_no_extra_fields
133
+ if persisted?
134
+ data_model.update(self)
135
+ else
136
+ @id = data_model.store(self)
137
+ end
138
+ end
139
+
140
+ # Sets values (for assignable fields only, defined by calling .field or .fields) in the hash
141
+ # to the object.
142
+ #
143
+ # Allows the proxy to have whitelisted attributes to be assigned from http requests.
144
+ def attributes=(hash={})
145
+ assignable_fields.each do |field|
146
+ send("#{field}=", hash[field]) if hash[field]
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ # Ensures that the data model has all of the fields that are trying to be saved. Raises an
153
+ # exception if the data model does not.
154
+ def ensure_no_extra_fields
155
+ extra_fields = (fields - data_model.data_fields)
156
+ if extra_fields.any?
157
+ raise "Tried to save a #{self.class.name} with fields not specified in #{data_model.name}: #{extra_fields.join(', ')}"
158
+ end
159
+ end
160
+ end
161
+ end
@@ -1,5 +1,42 @@
1
1
  require_relative '../../mince'
2
2
 
3
+ # = Shared example for a Mince Interface
4
+ #
5
+ # This is an Rspec Shared Example. It provides the ability to test
6
+ # shared behavior of objects without duplication.
7
+ #
8
+ # Use this shared example as documentation on what API a mince data interface
9
+ # must implement and as a specification and integration test while developing
10
+ # your mince data interface.
11
+ #
12
+ # == How to use
13
+ #
14
+ # For example. If I were writing a MySQL mince interface I would add mince
15
+ # as a gem dependency in my library (in your Gemfile or gemspec file). I would
16
+ # add Rspec, create a new spec file at spec/integration/mince_interface_spec.rb
17
+ # with the following contents
18
+ #
19
+ # require_relative '../../lib/my_mince_mysql'
20
+ # require 'mince/shared_examples/interface_example'
21
+ #
22
+ # describe 'Mince Interface with MySQL' do
23
+ # before do
24
+ # Mince::Config.interface = Mince::MyMinceMySQL::Interface
25
+ # end
26
+ #
27
+ # it_behaves_like 'a mince interface'
28
+ # end
29
+ #
30
+ # Run your spec
31
+ #
32
+ # bundle exec spec rspec/integration/mince_inerface_spect.rb
33
+ #
34
+ # Make the failures pass, when there are no failures your interface is fully
35
+ # supported. Be sure to submit your library when you've finished so others
36
+ # can use it.
37
+ #
38
+ # For a real examples, view the hashy_db, mingo, or mince_dynamo_db gems
39
+ #
3
40
  shared_examples_for 'a mince interface' do
4
41
  describe 'Mince Interface v2' do
5
42
  let(:interface) { Mince::Config.interface }
@@ -10,11 +47,15 @@ shared_examples_for 'a mince interface' do
10
47
  let(:data3) { { primary_key => 3, field_1: 'value 3', field_2: 9, shared_between_1_and_2: 'not the same as 1 and 2', :some_array => [1, 7]} }
11
48
 
12
49
  before do
13
- interface.set_data({})
50
+ interface.clear
14
51
 
15
52
  interface.insert(:some_collection, [data1, data2, data3])
16
53
  end
17
54
 
55
+ after do
56
+ interface.clear
57
+ end
58
+
18
59
  describe "Generating a primary key" do
19
60
  subject do
20
61
  (1..number_of_records).map do |salt|
@@ -40,7 +81,7 @@ shared_examples_for 'a mince interface' do
40
81
  it 'can delete a collection' do
41
82
  interface.delete_collection(:some_collection)
42
83
 
43
- interface.find_all(:some_collection).should == []
84
+ interface.find_all(:some_collection).to_a.should == []
44
85
  end
45
86
 
46
87
  it 'can delete records that match a given set of fields' do
@@ -48,85 +89,94 @@ shared_examples_for 'a mince interface' do
48
89
 
49
90
  interface.delete_by_params(:some_collection, params)
50
91
 
51
- interface.find_all(:some_collection).should == [data2, data3]
92
+ convert_each(interface.find_all(:some_collection)).should == convert_each([data2, data3])
52
93
  end
53
94
 
54
95
  it 'can write and read data to and from a collection' do
55
- data4 = {primary_key =>3, field_1: 'value 3', field_2: 9, shared_between_1_and_2: 'not the same as 1 and 2', :some_array => [1, 7]}
96
+ data4 = {primary_key =>4, field_1: 'value 3', field_2: 9, shared_between_1_and_2: 'not the same as 1 and 2', :some_array => [1, 7]}
56
97
 
57
98
  interface.add(:some_collection, data4)
58
- interface.find_all(:some_collection).should == [data1, data2, data3, data4]
99
+ convert_each(interface.find_all(:some_collection)).should == convert_each([data1, data2, data3, data4])
59
100
  end
60
101
 
61
102
  it 'can replace a record' do
62
103
  data2[:field_1] = 'value modified'
63
104
  interface.replace(:some_collection, data2)
64
105
 
65
- interface.find(:some_collection, primary_key, 2)[:field_1].should == 'value modified'
106
+ convert(interface.find(:some_collection, primary_key, 2))[:field_1].should == 'value modified'
66
107
  end
67
108
 
68
109
  it 'can update a field with a value on a specific record' do
69
110
  interface.update_field_with_value(:some_collection, 3, :field_2, '10')
70
-
71
- interface.find(:some_collection, primary_key, 3)[:field_2].should == '10'
111
+
112
+ convert(interface.find(:some_collection, primary_key, 3))[:field_2].should == '10'
72
113
  end
73
114
 
74
115
  it 'can increment a field with a given amount for a specific field' do
75
116
  interface.increment_field_by_amount(:some_collection, 1, :field_2, 3)
76
-
77
- interface.find(:some_collection, primary_key, 1)[:field_2].should == 6
117
+
118
+ convert(interface.find(:some_collection, primary_key, 1))[:field_2].should == 6
78
119
  end
79
120
 
80
121
  it 'can get one document' do
81
- interface.find(:some_collection, :field_1, 'value 1').should == data1
82
- interface.find(:some_collection, :field_2, 6).should == data2
122
+ convert(interface.find(:some_collection, :field_1, 'value 1')).should == convert(data1)
123
+ convert(interface.find(:some_collection, :field_2, 6)).should == convert(data2)
83
124
  end
84
125
 
85
126
  it 'can clear the data store' do
86
127
  interface.clear
87
128
 
88
- interface.find_all(:some_collection).should == []
129
+ interface.find_all(:some_collection).to_a.should == []
89
130
  end
90
131
 
91
132
  it 'can get all records of a specific key value' do
92
- interface.get_all_for_key_with_value(:some_collection, :shared_between_1_and_2, 'awesome_value').should == [data1, data2]
133
+ convert_each(interface.get_all_for_key_with_value(:some_collection, :shared_between_1_and_2, 'awesome_value')).should == convert_each([data1, data2])
93
134
  end
94
135
 
95
136
  it 'can get all records where a value includes any of a set of values' do
96
- interface.containing_any(:some_collection, :some_array, []).should == []
97
- interface.containing_any(:some_collection, :some_array, [7, 2, 3]).should == [data1, data3]
98
- interface.containing_any(:some_collection, primary_key, [1, 2, 5]).should == [data1, data2]
137
+ interface.containing_any(:some_collection, :some_array, []).to_a.should == []
138
+ convert_each(interface.containing_any(:some_collection, :some_array, [7, 2, 3])).should == convert_each([data1, data3])
139
+ convert_each(interface.containing_any(:some_collection, primary_key, [1, 2, 5])).should == convert_each([data1, data2])
99
140
  end
100
141
 
101
142
  it 'can get all records where the array includes a value' do
102
- interface.array_contains(:some_collection, :some_array, 1).should == [data1, data3]
103
- interface.array_contains(:some_collection, :some_array_2, 1).should == []
143
+ convert_each(interface.array_contains(:some_collection, :some_array, 1)).should == convert_each([data1, data3])
144
+ interface.array_contains(:some_collection, :some_array_2, 1).to_a.should == []
104
145
  end
105
146
 
106
147
  it 'can push a value to an array for a specific record' do
107
148
  interface.push_to_array(:some_collection, primary_key, 1, :field_3, 'add to existing array')
108
149
  interface.push_to_array(:some_collection, primary_key, 1, :new_field, 'add to new array')
109
150
 
110
- interface.find(:some_collection, primary_key, 1)[:field_3].should include('add to existing array')
111
- interface.find(:some_collection, primary_key, 1)[:new_field].should == ['add to new array']
151
+ record = convert(interface.find(:some_collection, primary_key, 1))
152
+ record[:field_3].should include('add to existing array')
153
+ record[:new_field].should == ['add to new array']
112
154
  end
113
155
 
114
156
  it 'can remove a value from an array for a specific record' do
115
157
  interface.remove_from_array(:some_collection, primary_key, 1, :field_3, 2)
116
158
 
117
- interface.find(:some_collection, primary_key, 1)[:field_3].should_not include(2)
159
+ convert(interface.find(:some_collection, primary_key, 1))[:field_3].should_not include(2)
118
160
  end
119
161
 
120
162
  it 'can get all records that match a given set of keys and values' do
121
- records = interface.get_by_params(:some_collection, field_1: 'value 1', shared_between_1_and_2: 'awesome_value')
163
+ records = interface.get_by_params(:some_collection, field_1: 'value 1', shared_between_1_and_2: 'awesome_value').to_a
122
164
  records.size.should be(1)
123
- records.first[primary_key].should == 1
124
- interface.find_all(:some_collection).size.should == 3
165
+ convert(records.first)[primary_key].should == 1
166
+ interface.find_all(:some_collection).to_a.size.should == 3
125
167
  end
126
168
 
127
169
  it 'can get a record for a specific key and value' do
128
- interface.get_for_key_with_value(:some_collection, :field_1, 'value 1').should == data1
170
+ convert(interface.get_for_key_with_value(:some_collection, :field_1, 'value 1')).should == convert(data1)
129
171
  end
130
172
  end
173
+
174
+ def convert(hash)
175
+ HashWithIndifferentAccess.new(hash)
176
+ end
177
+
178
+ def convert_each(ary)
179
+ ary.map{|a| convert(a) }
180
+ end
131
181
  end
132
182