couchpillow 0.3.10 → 0.4.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: 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: