couchpillow 0.3.10 → 0.4.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: 3185ef13356f6eadae67b5b1e57d3af472a3e545
4
- data.tar.gz: 9954f18c4e2469738fabab9ff8e3c9cd0409e681
3
+ metadata.gz: cb55c110b2b546a414fc253680530a0be2d4525b
4
+ data.tar.gz: 8aee488a67a464972655088b9976777f4a7649ae
5
5
  SHA512:
6
- metadata.gz: ffd1543a372cd2fa2431d70b9210532ad04ed94398d8b4a54b1ed233070eaeb6dafcc8437e687790a954d3b9a5636e644e50b045ad4e70ab72c3803a47423013
7
- data.tar.gz: cedf8d10d9b414c100797b505667da6010d8e6b0088bc73d2646e33456ea859d29b86a3dac0f4958da4fc656631be1de9fb940ffcb7568c3e09d077b14845611
6
+ metadata.gz: d30b27dc47e70736de216834c2c93721dc536809f90adb06942daedf5ab6d98da0c04c438153a6defe3ecca3737c7599d96279f88335162d7ac831aab71b5c85
7
+ data.tar.gz: 1c3e5a11b8098a3038e00026a3fc9c97060aaa4f6e50c189c9d4988f46fe689d9a3d29a37d5bcf320342122300b8278a7dcf3c22278796983a5bd5c1b0073967
data/README.markdown CHANGED
@@ -30,89 +30,108 @@ long as they `respond_to?` the `set`, `delete`, `replace`, and `get` methods.
30
30
 
31
31
  require 'couchpillow'
32
32
 
33
+ class MyDocument < CouchPillow::Document
34
+ type :my_document
35
+ attribute(:stuff)
36
+ end
37
+
33
38
  CouchPillow.db = Couchbase.connect( bucket: 'default', host: 'localhost' )
34
- doc = CouchPillow::Document.new( { :stuff => 'hello' }, '123' )
39
+ doc = CouchPillow::Document.new( { :stuff => 'hello' } )
35
40
  doc.save!
36
41
 
37
42
  # {
38
- # '_type': 'default',
43
+ # '_id': 'my_document_fb579b265cc005c47ff420a5c2a15d2b',
44
+ # '_type': 'my_document',
39
45
  # 'stuff': 'hello',
40
- # 'created_at': '2014-07-04 00:00:00 UTC'
41
- # 'updated_at': '2014-07-04 00:00:00 UTC'
46
+ # '_created_at': '2014-07-04 00:00:00 UTC'
47
+ # '_updated_at': '2014-07-04 00:00:00 UTC'
42
48
  # }
43
49
 
44
50
 
45
51
  Retrieving Documents:
46
52
 
47
- doc = CouchPillow::Document.get('123')
53
+ doc = MyDocument.get('123')
48
54
  doc.stuff # 'hello'
49
-
50
55
 
51
- Overriding `CouchPillow::Document`:
56
+ Specifying custom id:
52
57
 
53
58
  class User < CouchPillow::Document
54
59
  type :user
60
+ attribute(:email)
55
61
  end
56
62
 
57
63
  CouchPillow.db = Couchbase.connect( bucket: 'default', host: 'localhost' )
58
- doc = User.new( { :email => 'john@email.com' } )
64
+ doc = User.new( { :email => 'john@email.com' }, '123' )
59
65
  doc.email # 'john@email.com'
60
66
  doc.save!
61
67
 
62
68
  # {
63
- # '_id': 'fb579b265cc005c47ff420a5c2a15d2b',
69
+ # '_id': '123',
64
70
  # '_type': 'user',
65
71
  # 'email': 'john@email.com',
66
72
  # 'created_at': '2014-07-04 00:00:00 UTC'
67
73
  # 'updated_at': '2014-07-04 00:00:00 UTC'
68
74
  # }
69
75
 
70
- Using built-in validation methods:
76
+ ### Attributes
77
+
78
+ Using Attribute Directives:
71
79
 
72
80
  class User < CouchPillow::Document
73
81
  type :user
74
- validate_presence :email
75
- validate_type :first_name, String
82
+ attribute(:email)
83
+ .required
84
+
85
+ attribute(:first_name)
86
+ .type(String)
76
87
  end
77
88
 
78
89
  CouchPillow.db = Couchbase.connect( bucket: 'default', host: 'localhost' )
79
90
  doc = User.new( { :first_name => 'John' } )
80
- doc.save! # raises ValidationError('email is missing')
91
+ doc.save! # raises ValidationError "Attribute 'email' is missing"
81
92
  doc.email = 'john@email.com'
82
93
  doc.save! # Success!
83
94
 
84
- Using custom validation blocks:
95
+ List of Attribute Directives:
85
96
 
86
- class User < CouchPillow::Document
87
- type :user
88
- validate :phone, 'is not a number', lambda { |v| v.is_a? Numeric }
89
- end
97
+ * `required`
90
98
 
91
- CouchPillow.db = Couchbase.connect( bucket: 'default', host: 'localhost' )
92
- doc = User.new
93
- doc.phone = '123'
94
- doc.save! # raises ValidationError('phone is not a number')
95
- doc.phone = 123
96
- doc.save! # Success!
99
+ Sets this attribute as required. Will raise an error if attribute is missing
100
+ upon `save!` or `update!`
101
+
102
+ * `type(T)`
97
103
 
98
- Using `rename` to rename keys. Useful to maintain document integrity.
104
+ Sets the type of this attribute. Will perform type check if specified.
105
+
106
+ * `auto_convert`
107
+
108
+ Enables auto-conversion to the specified type. This gets ignored if `type`
109
+ directive is not specified.
110
+
111
+ * `default(&block)`
112
+
113
+ Runs the block to set the default value for this attribute, if it's missing
114
+ or nil.
115
+
116
+ * `content(&block)`
117
+
118
+ Custom validation method to check the value of the attribute. This is useful
119
+ in cases where you only want certain values to be stored (e.g a number between
120
+ 1-10 only)
121
+
122
+
123
+ ### Migration
124
+
125
+ Using `rename` to rename keys. Useful to maintain document integrity
126
+ from a migration.
99
127
 
100
128
  class User < CouchPillow::Document
101
129
  rename :username, :nickname
130
+ attribute(:nickname)
102
131
  end
103
132
  u = User.new( { :username => 'jdoe' } )
104
133
  u.nickname # 'jdoe'
105
134
 
106
- Using `whitelist` to whitelist keys. Also useful to maintain document integrity
107
- and keeping other fields from being saved. Keep in mind that when loading
108
- existing documents and if they have keys that are not listed on the whitelist,
109
- those keys will be dropped upon `save!`.
110
-
111
- class User < CouchPillow::Document
112
- whitelist :name
113
- end
114
- u = User.new( { :age => 10, :name => 'John' } )
115
- u.has?(:age) # false
116
135
 
117
136
 
118
137
  ## Design Docs and Views
@@ -123,5 +142,5 @@ that returns documents as values, you can easily use it like this:
123
142
 
124
143
  CouchPillow.db.design_docs['my_design_doc'].
125
144
  by_email(:body => { :key => 'john@email.com' }).map do |v|
126
- new User(v.value, v.id)
145
+ new User(v.doc, v.id)
127
146
  end
@@ -0,0 +1,153 @@
1
+ module CouchPillow
2
+
3
+ class Attribute
4
+
5
+ attr_reader :name
6
+
7
+ @required = false
8
+ @type = nil
9
+ @auto_convert = false
10
+ @default_block = nil
11
+ @check_value_message = nil
12
+ @check_value_block = nil
13
+
14
+
15
+ def initialize name
16
+ @name = name.to_s.to_sym
17
+ end
18
+
19
+
20
+ # Directive to mark this Attribute as required.
21
+ #
22
+ def required
23
+ @required = true
24
+ self
25
+ end
26
+
27
+
28
+ def required?
29
+ @required
30
+ end
31
+
32
+
33
+ # Directive to enforce the data type of this Attribute.
34
+ #
35
+ def type t
36
+ @type = t
37
+ self
38
+ end
39
+
40
+
41
+ # Attempts to auto convert values to the type specified by the {type}
42
+ # directive if the value is not of the same type.
43
+ # Has no effect if {type} is not specified.
44
+ #
45
+ def auto_convert
46
+ @auto_convert = true
47
+ self
48
+ end
49
+
50
+
51
+ # Directive to set the default value of this Attribute. Once specified, if
52
+ # this Attribute does not exist during the Document initialization, whether
53
+ # that's from {Document.get} or {Document#initialize}, the value of the
54
+ # Attribute will be set to the value returned by the block.
55
+ #
56
+ # @yield Sets the value of this Attribute to the value returned by the
57
+ # block.
58
+ #
59
+ def default &block
60
+ @default_block = block
61
+ self
62
+ end
63
+
64
+
65
+ def has_default?
66
+ @default_block != nil
67
+ end
68
+
69
+
70
+ # Directive to perform a validation over the value of this Attribute.
71
+ #
72
+ # @param message Message when block passed fails. Optional.
73
+ # @yield [v] Value of the Attribute.
74
+ #
75
+ # @example
76
+ # content("Name must be John") { |v| v == "John" }
77
+ # content { |v| v == "John" }
78
+ #
79
+ def content message = nil, &block
80
+ @check_value_message = message
81
+ @check_value_block = block
82
+ self
83
+ end
84
+
85
+
86
+ # Check the default value.
87
+ #
88
+ # @param value The value of this attribute to validate.
89
+ #
90
+ def trigger_default_directive
91
+ @default_block.call if has_default?
92
+ end
93
+
94
+
95
+ # Check value.
96
+ #
97
+ def trigger_content_directive value
98
+ if @check_value_block
99
+ raise ValidationError, @check_value_message unless
100
+ @check_value_block.call(value)
101
+ end
102
+ value
103
+ end
104
+
105
+
106
+ # Check type.
107
+ #
108
+ def trigger_type_directive value
109
+ if @type
110
+ # Run auto-conversion first.
111
+ if @auto_convert
112
+ if @type == Integer
113
+ value = Integer(value)
114
+ elsif @type == Float
115
+ value = Float(value)
116
+ elsif @type == String
117
+ value = String(value)
118
+ elsif @type == Array
119
+ value = Array(value)
120
+ elsif @type == Time && !value.is_a?(Time)
121
+ value = Time.parse(value)
122
+ elsif @type == CouchPillow::Boolean
123
+ value = value == 0 || value.to_s.downcase == "false" || !value ? false : true
124
+ end
125
+ end
126
+
127
+ if @type == CouchPillow::Boolean
128
+ raise ValidationError unless !!value == value
129
+ else
130
+ raise ValidationError unless value.is_a?(@type)
131
+ end
132
+ end
133
+
134
+ value
135
+ end
136
+
137
+
138
+ # Run the validation directives, except required directive.
139
+ # First it executes the {default} directive, then {auto_convert} to type,
140
+ # then {type} validation, then finally the {content} directive.
141
+ #
142
+ # @return The final value after the validation.
143
+ #
144
+ def validate value
145
+ value = trigger_default_directive if value.nil?
146
+ trigger_content_directive(trigger_type_directive(value))
147
+ end
148
+
149
+
150
+ end
151
+ end
152
+
153
+
@@ -0,0 +1,37 @@
1
+ module CouchPillow
2
+
3
+ module Attributive
4
+
5
+ # Declares a new Attribute
6
+ #
7
+ def attribute attr
8
+ attr = attr.to_s.to_sym
9
+ new_attr = Attribute.new(attr)
10
+ attributes[attr] = new_attr
11
+
12
+ # Define accessor methods
13
+ define_method(attr) do
14
+ @data[attr]
15
+ end
16
+ define_method("#{attr}=") do |val|
17
+ @data[attr] = val
18
+ end
19
+
20
+ new_attr
21
+ end
22
+
23
+
24
+ def attributes
25
+ @attributes ||= {}
26
+ end
27
+
28
+
29
+ def inherited(subclass)
30
+ # Copy existing attributes to subclasses
31
+ attributes.each do |k, v|
32
+ subclass.attributes[k] = v
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ module CouchPillow
2
+
3
+ # Faux Boolean class used for type checking and conversion.
4
+ #
5
+ class Boolean
6
+ end
7
+
8
+ end
@@ -2,45 +2,36 @@ module CouchPillow
2
2
 
3
3
  class Document
4
4
 
5
- # Validation Error.
6
- #
7
- class ValidationError < StandardError; end
8
-
9
-
10
- # Faux Boolean class used for type checking and conversion.
11
- #
12
- class Boolean; end
13
-
5
+ extend Attributive
14
6
 
15
7
  attr_reader :id
16
8
 
9
+ RESERVED_KEYS = %i[_id _type _created_at _updated_at]
17
10
 
18
- @type = "default"
11
+ DEFAULT_TYPE = "default".freeze
19
12
 
20
13
 
21
- PRESENCE_LAMBDA = lambda do |value| !value.nil? end
14
+ attribute(:_created_at)
15
+ .required
16
+ .type(Time).auto_convert
17
+ .default { Time.now.utc }
22
18
 
23
- RESERVED_KEYS = %i[_id _type created_at updated_at]
19
+ attribute(:_updated_at)
20
+ .required
21
+ .type(Time).auto_convert
22
+ .default { Time.now.utc }
24
23
 
25
24
 
26
- def initialize hash = {}, id = SecureRandom.hex
25
+ def initialize hash = {}, id = "#{self.class._type}_#{SecureRandom.hex}"
27
26
  @data = self.class.symbolize(hash)
28
- @id = id
29
27
 
30
- @data[:created_at] and
31
- @data[:created_at].is_a? String and
32
- @data[:created_at] = Time.parse(@data[:created_at]) or
33
- @data[:created_at] = Time.now.utc
34
-
35
- @data[:updated_at] and
36
- @data[:updated_at].is_a? String and
37
- @data[:updated_at] = Time.parse(@data[:updated_at])
28
+ @id = id
29
+ time = Time.now.utc
30
+ @data[:_created_at] ||= time
31
+ @data[:_updated_at] = time
38
32
 
39
- raise TypeError if @data[:_type] && @data[:_type] != self.class._type
40
- @data[:_type] = self.class._type
41
33
  rename!
42
34
  whitelist!
43
- ensure_types!
44
35
  end
45
36
 
46
37
 
@@ -54,38 +45,10 @@ module CouchPillow
54
45
  end
55
46
 
56
47
 
57
- # @private
58
- # Map hash keys to methods
59
- #
60
- def method_missing m, *args, &block
61
- ms = m.to_s
62
- if ms.end_with?("=")
63
- ms.gsub!('=', '')
64
- @data[ms.to_sym] = args[0]
65
- else
66
- if @data.has_key?(m)
67
- @data[m]
68
- else
69
- super
70
- end
71
- end
72
- end
73
-
74
-
75
- # @private
76
- #
77
- def respond_to? m
78
- ms = m.to_s
79
- return true if ms.end_with?("=")
80
- @data.has_key?(m) or
81
- super
82
- end
83
-
84
-
85
48
  # @private
86
49
  #
87
50
  def timestamp!
88
- @data[:updated_at] = Time.now.utc
51
+ @data[:_updated_at] = Time.now.utc
89
52
  end
90
53
 
91
54
 
@@ -93,10 +56,13 @@ module CouchPillow
93
56
  #
94
57
  def save!
95
58
  whitelist!
96
- validate
59
+ validate!
97
60
  sort!
98
61
  timestamp!
99
- CouchPillow.db.set @id, @data
62
+ to_save = @data.merge({
63
+ :_type => self.class._type
64
+ })
65
+ CouchPillow.db.set @id, to_save
100
66
  end
101
67
 
102
68
 
@@ -107,34 +73,32 @@ module CouchPillow
107
73
  end
108
74
 
109
75
 
110
- # Sort keys on this document.
111
- #
112
- def sort!
113
- @data = @data.sort.to_h
114
- end
115
-
116
-
117
76
  # Attempt to update this Document. Fails if this Document does not yet
118
77
  # exist in the database.
119
78
  #
120
79
  def update!
121
80
  whitelist!
122
- validate
81
+ validate!
123
82
  sort!
124
83
  timestamp!
125
- CouchPillow.db.replace @id, @data
84
+ to_save = @data.merge({
85
+ :_type => self.class._type
86
+ })
87
+ CouchPillow.db.replace @id, to_save
126
88
  end
127
89
 
128
90
 
129
91
  # Updates the attributes in the document.
130
92
  # Existing attributes will be overwritten and new ones will be added.
131
- # Existing attributes that are not present in the hash will be ignored.
93
+ # Any other existing attributes that are not present in the hash will be ignored.
132
94
  #
133
95
  def update hash
134
96
  hash.each do |k,v|
135
97
  @data[k.to_sym] = v
136
98
  end
99
+ rename!
137
100
  whitelist!
101
+ validate!
138
102
  end
139
103
 
140
104
 
@@ -149,15 +113,7 @@ module CouchPillow
149
113
 
150
114
 
151
115
  def to_hash
152
- { :_id => @id }.merge!(@data)
153
- end
154
-
155
-
156
- def validate
157
- self.class.validate_keys.each do |k, msg, method|
158
- raise ValidationError, "[#{k}, #{@data[k]}] #{msg}" unless
159
- @data.has_key?(k) && method.call(@data[k])
160
- end
116
+ { :_id => @id, :_type => self.class._type }.merge!(@data)
161
117
  end
162
118
 
163
119
 
@@ -172,63 +128,61 @@ module CouchPillow
172
128
  end
173
129
 
174
130
 
175
- # Ensure types of the values in the Document are converted to the type
176
- # specified in the {validate_type} method.
131
+ # Cleanup the @data hash so it only contains relevant fields.
177
132
  #
178
- def ensure_types!
179
- self.class.type_keys.each do |k, t|
180
- if value = @data[k] and !value.is_a?(t)
181
- if t == Integer
182
- @data[k] = Integer(value)
183
- elsif t == Float
184
- @data[k] = Float(value)
185
- elsif t == String
186
- @data[k] = String(value)
187
- elsif t == Array
188
- @data[k] = Array(value)
189
- elsif t == Time
190
- @data[k] = Time.parse(value)
191
- elsif t == Boolean
192
- @data[k] = value == 0 || !value ? false : true
193
- end
194
- end
133
+ def whitelist!
134
+ @data.delete_if do |k, v|
135
+ !self.class.attributes.has_key?(k)
195
136
  end
196
137
  end
197
138
 
198
139
 
199
- # Filter this Document through the whitelist as specified by the
200
- # {whitelist} directive.
140
+ # Go through each attribute, and validate the values.
141
+ # Validation also perform auto-conversion if auto-conversion is enabled
142
+ # for that attribute.
201
143
  #
202
- def whitelist!
203
- unless self.class.whitelist_keys.empty?
204
- @data.select! do |k, v|
205
- RESERVED_KEYS.include?(k) ||
206
- self.class.whitelist_keys.include?(k)
144
+ def validate!
145
+ self.class.attributes.each do |k, attr|
146
+ if has?(k)
147
+ @data[k] = attr.validate(@data[k]) if has?(k)
148
+ else
149
+ @data[k] = attr.trigger_default_directive if attr.has_default?
150
+ raise ValidationError, "Attribute '#{k}' is required" if attr.required? && !has?(k)
207
151
  end
208
152
  end
209
153
  end
210
154
 
211
155
 
156
+ # Sort keys on this document.
157
+ #
158
+ def sort!
159
+ @data = @data.sort.to_h
160
+ end
161
+
162
+
163
+ def _type
164
+ self.class._type
165
+ end
166
+
167
+
168
+ def _id
169
+ @id
170
+ end
171
+
172
+
212
173
  # Get a Document given an id.
213
174
  #
214
- # @return nil if Document is of a different type.
175
+ # @return nil if not found or Document is of a different type.
215
176
  #
216
177
  def self.get id
217
178
  result = CouchPillow.db.get(id) and
218
- type = result['_type'] and
219
- type == self._type and
179
+ type = result[:_type] || result["_type"] and
180
+ type == _type and
220
181
  new(result, id) or
221
182
  nil
222
183
  end
223
184
 
224
185
 
225
- # Sets the type of this Document.
226
- #
227
- def self.type value
228
- @type = value.to_s
229
- end
230
-
231
-
232
186
  # Rename an existing key to a new key. This is invoked right after
233
187
  # initialize.
234
188
  #
@@ -240,80 +194,18 @@ module CouchPillow
240
194
  end
241
195
 
242
196
 
243
- # Validate the presence of a particular key, and the value of that key
244
- # cannot be nil.
245
- #
246
- def self.validate_presence key
247
- validate key, "is missing", PRESENCE_LAMBDA
248
- end
249
-
250
-
251
- # Validate the type of a particular key.
252
- #
253
- # @example
254
- # validate_type :name, String
255
- #
256
- def self.validate_type key, type
257
- validate key, "#{key} is not the correct type. Expected a #{type}", lambda { |v| v.is_a? type }
258
- type_keys << [key, type]
259
- end
260
-
261
-
262
- # Validate the type is a Boolean, since Ruby lacks a Boolean class.
263
- #
264
- # @example
265
- # validate_type_boolean :available, Boolean
266
- #
267
- def self.validate_type_boolean key
268
- validate key, "#{key} is not the correct type. Expected a Boolean", lambda { |v| !!v == v }
269
- type_keys << [key, Boolean]
270
- end
271
-
272
-
273
- # Validate the presence of a particular key using a custom validation method.
274
- #
275
- # @param key Key to be validated.
276
- # @param message Message that will be displayed when validation fails.
277
- # @yield [v] Value of the key.
278
- # The block must return truthy for it to pass the validation.
279
- #
280
- # @example
281
- # validate :first_name, 'first name is not Joe', lambda { |v| v != "Joe" }
282
- #
283
- def self.validate key, message, block
284
- raise ValidationError, "Provide validation method for key #{key}" unless block
285
- validate_keys << [key, message, block]
286
- end
287
-
288
-
289
- # This Document should only accept keys that are specified here.
290
- # The existence of these keys are optional, and won't trigger any validation
291
- # unless specified by the validate methods. Hashes passed to {#update} and
292
- # {#initialize} will be filtered through this list.
293
- #
294
- # If you don't specify a whitelist, Document will accept any keys, but
295
- # once you specify it, only those keys will be accepted and the rest will
296
- # be dropped.
297
- #
298
- # @param list Whitelist of keys.
299
- #
300
- # @example
301
- # whitelist :first_name, :last_name, :address
197
+ # Sets the type of this Document.
302
198
  #
303
- def self.whitelist *list
304
- list.flatten.each do |k|
305
- whitelist_keys << k.to_s.to_sym
306
- end
199
+ def self.type value
200
+ @type = value.to_s
307
201
  end
308
202
 
309
203
 
310
204
  private
311
205
 
312
206
 
313
- # Read the type of this Document. Internal use only.
314
- #
315
207
  def self._type
316
- @type
208
+ @type ||= DEFAULT_TYPE
317
209
  end
318
210
 
319
211
 
@@ -322,26 +214,12 @@ module CouchPillow
322
214
  end
323
215
 
324
216
 
325
- def self.type_keys
326
- @type_keys ||= []
327
- end
328
-
329
-
330
- def self.validate_keys
331
- @validate_keys ||= []
332
- end
333
-
334
-
335
- def self.whitelist_keys
336
- @whitelist_keys ||= []
337
- end
338
-
339
-
340
217
  def self.symbolize hash
341
218
  hash.inject({}) do |memo,(k,v)|
342
219
  memo[k.to_sym] = v
343
220
  memo
344
221
  end
345
222
  end
223
+
346
224
  end
347
225
  end
@@ -0,0 +1,8 @@
1
+ module CouchPillow
2
+
3
+ # Validation Error.
4
+ #
5
+ class ValidationError < StandardError
6
+ end
7
+
8
+ end
@@ -1,5 +1,5 @@
1
1
  module CouchPillow
2
2
  GEM_NAME = "couchpillow"
3
3
  NAME = "CouchPillow"
4
- VERSION = "0.3.10"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/couchpillow.rb CHANGED
@@ -14,5 +14,9 @@ module CouchPillow
14
14
 
15
15
  end
16
16
 
17
+ require 'couchpillow/attribute'
18
+ require 'couchpillow/attributive'
19
+ require 'couchpillow/validation_error'
20
+ require 'couchpillow/boolean'
17
21
  require 'couchpillow/document'
18
22
  require 'couchpillow/version'
data/test/helper.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  require './lib/couchpillow.rb'
2
2
 
3
+ require 'minitest/autorun'
4
+ require 'minitest/unit'
5
+ require 'mocha/mini_test'
6
+
3
7
 
4
8
  class FakeCouchbaseServer
5
9
 
@@ -0,0 +1,122 @@
1
+ require './test/helper.rb'
2
+
3
+ class TestAttribute < Minitest::Test
4
+
5
+ Attribute = CouchPillow::Attribute
6
+
7
+
8
+ def test_required
9
+ attr = Attribute.new(:batman).required
10
+ assert_equal true, attr.required?
11
+ end
12
+
13
+
14
+ def test_type
15
+ attr = Attribute.new(:batman).type(String)
16
+ assert_equal "hello", attr.validate("hello")
17
+ assert_raises CouchPillow::ValidationError do
18
+ attr.validate(1)
19
+ end
20
+ end
21
+
22
+
23
+ def test_auto_convert_integer
24
+ attr = Attribute.new(:batman).type(Integer).auto_convert
25
+ assert_equal 1000, attr.validate(1000)
26
+ assert_equal 1000, attr.validate("1000")
27
+ end
28
+
29
+
30
+ def test_auto_convert_float
31
+ attr = Attribute.new(:batman).type(Float).auto_convert
32
+ assert_equal 1000.01, attr.validate(1000.01)
33
+ assert_equal 1000.01, attr.validate("1000.01")
34
+ end
35
+
36
+
37
+ def test_auto_convert_string
38
+ attr = Attribute.new(:batman).type(String).auto_convert
39
+ assert_equal "hello", attr.validate("hello")
40
+ assert_equal "1", attr.validate(1)
41
+ end
42
+
43
+
44
+ def test_auto_convert_array
45
+ attr = Attribute.new(:batman).type(Array).auto_convert
46
+ assert_equal ["hello"], attr.validate(["hello"])
47
+ assert_equal [1], attr.validate(1)
48
+ end
49
+
50
+
51
+ def test_auto_convert_time
52
+ attr = Attribute.new(:batman).type(Time).auto_convert
53
+ time = attr.validate("2014/7/14")
54
+ assert time.is_a?(Time)
55
+ assert_equal 2014, time.year
56
+ assert_equal 7, time.month
57
+ assert_equal 14, time.day
58
+
59
+ time = attr.validate(Time.now.utc)
60
+ assert time
61
+ end
62
+
63
+
64
+ def test_auto_convert_boolean
65
+ attr = Attribute.new(:batman).type(CouchPillow::Boolean).auto_convert
66
+ assert_equal false, attr.validate("false")
67
+ assert_equal true, attr.validate("123")
68
+ assert_equal true, attr.validate(1)
69
+ assert_equal false, attr.validate(0)
70
+ assert_equal false, attr.validate(nil)
71
+ end
72
+
73
+
74
+ def test_default
75
+ attr = Attribute.new(:batman).default { "John" }
76
+ assert_equal "John", attr.validate(nil)
77
+ end
78
+
79
+
80
+ def test_has_default
81
+ attr = Attribute.new(:batman).default { "John" }
82
+ assert_equal true, attr.has_default?
83
+
84
+ no_default_attr = Attribute.new(:batman)
85
+ assert_equal false, no_default_attr.has_default?
86
+ end
87
+
88
+
89
+ def test_content
90
+ attr = Attribute.new(:batman).content { |v| v == "John" }
91
+ assert_raises CouchPillow::ValidationError do
92
+ attr.validate("Jim")
93
+ end
94
+ attr.validate("John")
95
+ end
96
+
97
+
98
+ def test_all
99
+ attr = Attribute.new(:batman)
100
+ .required
101
+ .type(Integer)
102
+ .default { 0 }
103
+ .content { |v| v >= 0 }
104
+
105
+ assert_equal true, attr.required?
106
+ assert_equal 0, attr.validate(nil)
107
+ assert_equal 100, attr.validate(100)
108
+ assert_raises CouchPillow::ValidationError do
109
+ attr.validate(-100)
110
+ end
111
+ end
112
+
113
+
114
+ def test_validate_maintain_value_if_type_not_specified
115
+ attr = Attribute.new(:batman)
116
+ [true, false, :symbol, 1, 0, -1, 10.0, "string", 1293.1, 0x90, Time.now, nil].each do |v|
117
+ assert_equal v, attr.validate(v)
118
+ end
119
+ end
120
+
121
+
122
+ end
@@ -1,6 +1,3 @@
1
- require 'minitest/autorun'
2
- require 'minitest/unit'
3
- require 'mocha/mini_test'
4
1
  require './test/helper.rb'
5
2
 
6
3
  class TestDocument < Minitest::Test
@@ -29,12 +26,12 @@ class TestDocument < Minitest::Test
29
26
  def test_timestamp
30
27
  d = Document.new({}, "1")
31
28
  d.save!
32
- assert d.created_at
33
- assert d.updated_at
29
+ assert d._created_at
30
+ assert d._updated_at
34
31
  end
35
32
 
36
33
 
37
- def test_type
34
+ def test_default_type
38
35
  d = Document.new({}, "1")
39
36
  assert_equal "default", d._type
40
37
  end
@@ -51,16 +48,24 @@ class TestDocument < Minitest::Test
51
48
  def test_update
52
49
  d = Class.new(Document) do
53
50
  type 'test'
51
+
52
+ attribute(:me)
53
+ attribute(:batman)
54
+ attribute(:apple)
54
55
  end.new
56
+ refute d.me
57
+ refute d.batman
58
+ refute d.apple
59
+
55
60
  hash = {
56
61
  'me' => 'too',
57
62
  :batman => 'robin',
58
63
  'apple' => 123
59
64
  }
60
65
  d.update(hash)
61
- assert_equal 'too', d[:me]
62
- assert_equal 'robin', d[:batman]
63
- assert_equal 123, d[:apple]
66
+ assert_equal 'too', d.me
67
+ assert_equal 'robin', d.batman
68
+ assert_equal 123, d.apple
64
69
  assert d.save!
65
70
  end
66
71
 
@@ -68,8 +73,11 @@ class TestDocument < Minitest::Test
68
73
  def test_update_existing_fields
69
74
  d = Class.new(Document) do
70
75
  type 'test'
76
+ attribute(:a)
77
+ attribute(:b)
71
78
  end.new({ a: 1, b: 2})
72
79
  assert_equal 1, d[:a]
80
+ assert_equal 1, d.a
73
81
  hash = {
74
82
  'a' => 'too',
75
83
  }
@@ -80,200 +88,11 @@ class TestDocument < Minitest::Test
80
88
  end
81
89
 
82
90
 
83
- def test_validate_presence
84
- d = Class.new(Document) do
85
- validate_presence :xyz
86
- end.new
87
- assert_raises Document::ValidationError do
88
- d.save!
89
- end
90
-
91
- d.xyz = 10
92
- d.save!
93
- end
94
-
95
-
96
- def test_validate_type
97
- d = Class.new(Document) do
98
- validate_type :abc, Hash
99
- end.new
100
-
101
- d.abc = "other type"
102
- assert_raises Document::ValidationError do
103
- d.save!
104
- end
105
-
106
- d.abc = { :hello => "world" }
107
- d.save!
108
- end
109
-
110
-
111
- def test_validate_type_boolean
112
- d = Class.new(Document) do
113
- validate_type_boolean :abc
114
- end.new
115
-
116
- d.abc = "other type"
117
- assert_raises Document::ValidationError do
118
- d.save!
119
- end
120
-
121
- d.abc = true
122
- d.save!
123
- end
124
-
125
-
126
- def test_validate_type_also_ensures_types_on_create_integer
127
- klass = Class.new(Document) do
128
- type "test"
129
- validate_type :abc, Integer
130
- end
131
-
132
- CouchPillow.db.
133
- expects(:get).
134
- with('123').
135
- returns( { '_type' => 'test', 'abc' => '100' } )
136
-
137
- d = klass.get('123')
138
- assert d
139
- assert_equal 100, d.abc
140
- end
141
-
142
-
143
- def test_validate_type_also_ensures_types_on_create_string
144
- klass = Class.new(Document) do
145
- type "test"
146
- validate_type :abc, String
147
- end
148
-
149
- CouchPillow.db.
150
- expects(:get).
151
- with('123').
152
- returns( { '_type' => 'test', 'abc' => 100 } )
153
-
154
- d = klass.get('123')
155
- assert d
156
- assert_equal '100', d.abc
157
- end
158
-
159
-
160
- def test_validate_type_also_ensures_types_on_create_float
161
- klass = Class.new(Document) do
162
- type "test"
163
- validate_type :abc, Float
164
- end
165
-
166
- CouchPillow.db.
167
- expects(:get).
168
- with('123').
169
- returns( { '_type' => 'test', 'abc' => '121.21' } )
170
-
171
- d = klass.get('123')
172
- assert d
173
- assert_equal 121.21, d.abc
174
- end
175
-
176
-
177
- def test_validate_type_also_ensures_types_on_create_array
178
- klass = Class.new(Document) do
179
- type "test"
180
- validate_type :abc, Array
181
- end
182
-
183
- CouchPillow.db.
184
- expects(:get).
185
- with('123').
186
- returns( { '_type' => 'test', 'abc' => 1 } )
187
-
188
- d = klass.get('123')
189
- assert d
190
- assert_equal [1], d.abc
191
- end
192
-
193
-
194
- def test_validate_type_also_ensures_types_on_create_time
195
- klass = Class.new(Document) do
196
- type "test"
197
- validate_type :abc, Time
198
- end
199
-
200
- CouchPillow.db.
201
- expects(:get).
202
- with('123').
203
- returns( { '_type' => 'test', 'abc' => "2014/07/04" } )
204
-
205
- d = klass.get('123')
206
- assert d
207
- assert d.abc.is_a?(Time)
208
- assert_equal 2014, d.abc.year
209
- assert_equal 7, d.abc.month
210
- assert_equal 4, d.abc.day
211
- end
212
-
213
-
214
- def test_validate_type_also_ensures_types_on_create_boolean
215
- klass = Class.new(Document) do
216
- type "test"
217
- validate_type_boolean :abc
218
- end
219
-
220
- # true
221
- CouchPillow.db.
222
- expects(:get).
223
- with('123').
224
- returns( { '_type' => 'test', 'abc' => 1 } )
225
-
226
- d = klass.get('123')
227
- assert d
228
- assert_equal true, d.abc
229
-
230
- # false
231
- CouchPillow.db.
232
- expects(:get).
233
- with('123').
234
- returns( { '_type' => 'test', 'abc' => 0 } )
235
-
236
- d = klass.get('123')
237
- assert d
238
- assert_equal false, d.abc
239
- end
240
-
241
-
242
- def test_validate_custom
243
- d = Class.new(Document) do
244
- validate :xyz, "must be Numeric", lambda { |v| v.is_a? Numeric }
245
- end.new
246
- d.xyz = "string"
247
- assert_raises Document::ValidationError do
248
- d.save!
249
- end
250
-
251
- d.xyz = {}
252
- assert_raises Document::ValidationError do
253
- d.save!
254
- end
255
-
256
- d.xyz = 123
257
- d.save!
258
- end
259
-
260
-
261
- def test_whitelist
262
- d = Class.new(Document) do
263
- whitelist :a, :b, :c
264
- end.new
265
- d.update({a: 1, b: 2, c: 3, d: 4})
266
- assert d.save!
267
- assert_equal 1, d.a
268
- assert_equal 2, d.b
269
- assert_equal 3, d.c
270
- assert_equal false, d.has?(:d)
271
- end
272
-
273
-
274
- def test_whitelist_using_symbol_literal
91
+ def test_exclusion_other_keys
275
92
  d = Class.new(Document) do
276
- whitelist %i[a b c]
93
+ attribute :a
94
+ attribute :b
95
+ attribute :c
277
96
  end.new
278
97
  d.update({a: 1, b: 2, c: 3, d: 4})
279
98
  assert d.save!
@@ -287,14 +106,14 @@ class TestDocument < Minitest::Test
287
106
  def test_to_json
288
107
  mock_time
289
108
  d = Document.new({}, "1")
290
- assert_equal "{\"_id\":\"1\",\"created_at\":\"#{mock_time.to_s}\",\"_type\":\"default\"}", d.to_json
109
+ assert_equal "{\"_id\":\"1\",\"_type\":\"default\",\"_created_at\":\"#{mock_time.to_s}\",\"_updated_at\":\"#{mock_time.to_s}\"}", d.to_json
291
110
  end
292
111
 
293
112
 
294
113
  def test_to_hash
295
114
  mock_time
296
115
  d = Document.new({}, "1")
297
- assert_equal({ :_id => "1", :created_at => mock_time, :_type => "default" }, d.to_hash)
116
+ assert_equal({ :_id => "1", :_created_at => mock_time, :_type => "default", :_updated_at => mock_time }, d.to_hash)
298
117
  end
299
118
 
300
119
 
@@ -315,6 +134,9 @@ class TestDocument < Minitest::Test
315
134
 
316
135
  def test_rename_keys
317
136
  d = Class.new(Document) do
137
+ attribute(:bar)
138
+ attribute(:other)
139
+
318
140
  rename :foo, :bar
319
141
  end.new( { :foo => 123, :other => 'abc' } )
320
142
  assert_equal 123, d[:bar]
@@ -340,18 +162,34 @@ class TestDocument < Minitest::Test
340
162
 
341
163
  assert_raises ArgumentError do
342
164
  d = Class.new(Document) do
343
- rename :created_at, :err
165
+ rename :_created_at, :err
344
166
  end.new
345
167
  end
346
168
 
347
169
  assert_raises ArgumentError do
348
170
  d = Class.new(Document) do
349
- rename :bla, :updated_at
171
+ rename :bla, :_updated_at
350
172
  end.new
351
173
  end
352
174
  end
353
175
 
354
176
 
177
+ def test_get
178
+ klass = Class.new(Document) do
179
+ type 'test'
180
+ attribute(:foo)
181
+ end
182
+
183
+ k = klass.new
184
+ k.foo = 100
185
+ k.save!
186
+ k_id = k._id
187
+
188
+ d = klass.get(k_id)
189
+ assert_equal 100, d.foo
190
+ end
191
+
192
+
355
193
  def test_get_returns_nil
356
194
  CouchPillow.db.expects(:get).with('123').returns(nil)
357
195
  d = Document.get('123')
@@ -375,14 +213,96 @@ class TestDocument < Minitest::Test
375
213
 
376
214
 
377
215
  def test_key_with_false_values
378
- d = Document.new({ :key => false }, "1")
216
+ klass = Class.new(Document) do
217
+ attribute(:key)
218
+ end
219
+ d = klass.new({ :key => false }, "1")
379
220
  assert_equal false, d.key
380
221
  end
381
222
 
382
223
 
383
224
  def test_key_with_nil_values
384
- d = Document.new({ :key => nil }, "1")
225
+ klass = Class.new(Document) do
226
+ attribute(:key)
227
+ end
228
+ d = klass.new({ :key => nil }, "1")
385
229
  assert_equal nil, d.key
386
230
  end
387
231
 
232
+
233
+ def test_attribute
234
+ d = Class.new(Document) do
235
+ type 'test'
236
+ attribute(:foo)
237
+ end.new
238
+
239
+ d.foo = 12345
240
+ d.save!
241
+ end
242
+
243
+
244
+ def test_attribute_type
245
+ d = Class.new(Document) do
246
+ type 'test'
247
+ attribute(:foo)
248
+ .type(String)
249
+ end.new
250
+
251
+ d.foo = "test"
252
+ d.save!
253
+ end
254
+
255
+
256
+ def test_attribute_auto_convert
257
+ d = Class.new(Document) do
258
+ type 'test'
259
+ attribute(:foo)
260
+ .type(String).auto_convert
261
+ end.new
262
+
263
+ d.foo = 1
264
+ d.save!
265
+
266
+ assert_equal "1", d.foo
267
+ end
268
+
269
+
270
+ def test_attribute_with_directives
271
+ d = Class.new(Document) do
272
+ type 'test'
273
+ attribute(:foo)
274
+ .default { 100 }
275
+ end.new
276
+
277
+ d.save!
278
+ assert_equal 100, d.foo
279
+ end
280
+
281
+
282
+ def test_attribute_no_default_but_required
283
+ klass = Class.new(Document) do
284
+ type 'test'
285
+ attribute(:foo)
286
+ .required
287
+ end
288
+
289
+ k = klass.new
290
+ assert_raises CouchPillow::ValidationError do
291
+ k.save!
292
+ end
293
+ end
294
+
295
+
296
+ def test_attribute_with_default_but_also_required
297
+ klass = Class.new(Document) do
298
+ type 'test'
299
+ attribute(:foo)
300
+ .required
301
+ .default { "batman" }
302
+ end
303
+
304
+ k = klass.new
305
+ k.save!
306
+ end
307
+
388
308
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: couchpillow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.10
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Albert Tedja
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-06 00:00:00.000000000 Z
11
+ date: 2014-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -64,9 +64,14 @@ files:
64
64
  - Rakefile
65
65
  - couchpillow.gemspec
66
66
  - lib/couchpillow.rb
67
+ - lib/couchpillow/attribute.rb
68
+ - lib/couchpillow/attributive.rb
69
+ - lib/couchpillow/boolean.rb
67
70
  - lib/couchpillow/document.rb
71
+ - lib/couchpillow/validation_error.rb
68
72
  - lib/couchpillow/version.rb
69
73
  - test/helper.rb
74
+ - test/test_attribute.rb
70
75
  - test/test_document.rb
71
76
  homepage: https://github.com/atedja/pillow
72
77
  licenses:
@@ -88,9 +93,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
93
  version: '0'
89
94
  requirements: []
90
95
  rubyforge_project:
91
- rubygems_version: 2.4.2
96
+ rubygems_version: 2.4.5
92
97
  signing_key:
93
98
  specification_version: 4
94
99
  summary: Document wrapper for Couchbase
95
100
  test_files: []
96
- has_rdoc: