ohm-contrib 0.0.20 → 0.0.21

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/README.markdown CHANGED
@@ -68,6 +68,76 @@ Example usage
68
68
 
69
69
  Typecasting explained
70
70
  ---------------------
71
+
72
+ I studied various typecasting behaviors implemented by a few ORMs in Ruby.
73
+
74
+ ### ActiveRecord
75
+
76
+ class Post < ActiveRecord::Base
77
+ # say we have an integer column in the DB named votes
78
+ end
79
+ Post.new(:votes => "FooBar").votes == 0
80
+ # => true
81
+
82
+ ### DataMapper
83
+ class Post
84
+ include DataMapper::Resource
85
+
86
+ property :id, Serial
87
+ property :votes, Integer
88
+ end
89
+
90
+ post = Post.new(:votes => "FooBar")
91
+ post.votes == "FooBar"
92
+ # => true
93
+
94
+ post.save
95
+ post.reload
96
+
97
+ # Get ready!!!!
98
+ post.votes == 0
99
+ # => true
100
+
101
+ ### Ohm::Typecast approach.
102
+
103
+ #### Mindset:
104
+
105
+ 1. Explosion everytime is too cumbersome.
106
+ 2. Mutation of data is less than ideal (Also similar to MySQL silently allowing you
107
+ to store more than 255 chars in a VARCHAR and then truncating that data. Yes I know
108
+ you can configure it to be noisy but the defaults kill).
109
+ 3. We just want to operate on it like it should!
110
+
111
+ #### Short Demo:
112
+ class Post < Ohm::Model
113
+ include Ohm::Typecast
114
+ attribute :votes
115
+ end
116
+
117
+ post = Post.new(:votes => "FooBar")
118
+ post.votes == "FooBar"
119
+ # => true
120
+
121
+ post.save
122
+ post = Post[post.id]
123
+ post.votes == "FooBar"
124
+ # => true
125
+
126
+ # Here comes the cool part...
127
+ post.votes * 1
128
+ # => ArgumentError: invalid value for Integer: "FooBar"
129
+
130
+ post.votes = 50
131
+ post.votes * 2 == 100
132
+ # => true
133
+
134
+ post.votes.class == Ohm::Types::Integer
135
+ # => true
136
+ post.votes.inspect == "50"
137
+ # => true
138
+
139
+ #### More examples just to show the normal case.
140
+
71
141
  require 'ohm'
72
142
  require 'ohm/contrib'
73
143
 
@@ -77,9 +147,14 @@ Typecasting explained
77
147
  attribute :price, Decimal
78
148
  attribute :available_at, Time
79
149
  attribute :stock, Integer
150
+ attribute :address, Hash
151
+ attribute :tags, Array
80
152
  end
81
153
 
82
- post = Post.create(:price => "10.20", :stock => "100")
154
+ post = Post.create(:price => "10.20", :stock => "100",
155
+ :address => { "city" => "Boston", "country" => "US" },
156
+ :tags => ["redis", "ohm", "typecast"])
157
+
83
158
  post.price.to_s == "10.20"
84
159
  # => true
85
160
 
@@ -89,6 +164,23 @@ Typecasting explained
89
164
  post.stock / 10 == 10
90
165
  # => true
91
166
 
167
+ post.address["city"] == "Boston"
168
+ post.tags.map { |tag| tag.upcase }
169
+
170
+ # of course mutation works for both cases
171
+ post.price += 5
172
+ post.stock -= 1
173
+ post.tags << "contrib"
174
+ post.address["state"] = "MA"
175
+ post.save
176
+ post = Post[post.id]
177
+
178
+ post.address["state"] == "MA"
179
+ # => true
180
+ post.tags.include?("contrib")
181
+ # => true
182
+
183
+
92
184
  Credits
93
185
  -------
94
186
  Thanks to github user gnrfan for the web validations.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.20
1
+ 0.0.21
data/lib/ohm/contrib.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Ohm
2
2
  module Contrib
3
- VERSION = '0.0.20'
3
+ VERSION = '0.0.21'
4
4
  end
5
5
 
6
6
  autoload :Boundaries, "ohm/contrib/boundaries"
@@ -170,4 +170,4 @@ module Ohm
170
170
  end
171
171
  end
172
172
  end
173
- end
173
+ end
@@ -24,7 +24,7 @@ module Ohm
24
24
  def self.[](type)
25
25
  const_get(type.to_s.split('::').last)
26
26
  end
27
-
27
+
28
28
  class Base < BasicObject
29
29
  class Exception < ::Exception; end
30
30
 
@@ -36,23 +36,23 @@ module Ohm
36
36
 
37
37
  def self.[](value)
38
38
  return self::EMPTY if value.to_s.empty?
39
-
39
+
40
40
  new(value)
41
41
  end
42
42
 
43
43
  def self.delegate_to(klass, except = @@delegation_blacklist)
44
44
  methods = klass.public_instance_methods.map(&:to_sym) - except
45
- def_delegators :object, *methods
45
+ def_delegators :object, *methods
46
46
  end
47
47
 
48
48
  def inspect
49
- object.inspect
49
+ @raw.inspect
50
50
  end
51
51
  end
52
52
 
53
53
  class Primitive < Base
54
54
  EMPTY = nil
55
-
55
+
56
56
  def initialize(value)
57
57
  @raw = value
58
58
  end
@@ -64,7 +64,7 @@ module Ohm
64
64
  def ==(other)
65
65
  to_s == other.to_s
66
66
  end
67
-
67
+
68
68
  protected
69
69
  def object
70
70
  @raw
@@ -78,10 +78,6 @@ module Ohm
78
78
  class Decimal < Primitive
79
79
  delegate_to ::BigDecimal
80
80
 
81
- def inspect
82
- object.to_s('F')
83
- end
84
-
85
81
  protected
86
82
  def object
87
83
  ::Kernel::BigDecimal(@raw)
@@ -123,28 +119,36 @@ module Ohm
123
119
  ::Date.parse(@raw)
124
120
  end
125
121
  end
126
-
122
+
127
123
  class Serialized < Base
128
124
  attr :object
129
125
 
130
126
  def initialize(raw)
131
127
  @object = case raw
132
- when self.class::RAW then raw
133
- when ::String then ::JSON.parse(raw)
134
- when self.class then raw.object
128
+ when self.class::RAW
129
+ raw
130
+ when ::String
131
+ begin
132
+ ::JSON.parse(raw)
133
+ rescue ::JSON::ParserError
134
+ raw
135
+ end
136
+ when self.class
137
+ raw.object
135
138
  else
136
- ::Kernel.raise ::TypeError,
139
+ ::Kernel.raise ::TypeError,
137
140
  "%s does not accept %s" % [self.class, raw.inspect]
138
141
  end
139
142
  end
140
143
 
141
144
  def ==(other)
142
- object == other
145
+ object == other
143
146
  end
144
-
147
+
145
148
  def to_s
146
149
  object.to_json
147
150
  end
151
+ alias :inspect :to_s
148
152
  end
149
153
 
150
154
  class Hash < Serialized
@@ -163,9 +167,9 @@ module Ohm
163
167
  class Array < Serialized
164
168
  EMPTY = []
165
169
  RAW = ::Array
166
-
170
+
167
171
  delegate_to ::Array
168
-
172
+
169
173
  # @private since basic object doesn't include a #class we need
170
174
  # to define this manually
171
175
  def class
@@ -234,7 +238,7 @@ module Ohm
234
238
  # Defines a typecasted attribute.
235
239
  #
236
240
  # @example
237
- #
241
+ #
238
242
  # class User < Ohm::Model
239
243
  # include Ohm::Typecast
240
244
  #
@@ -258,9 +262,9 @@ module Ohm
258
262
  # user = User.new(:age => 20)
259
263
  # user.age - 1 == 19
260
264
  # => true
261
- #
265
+ #
262
266
  # @param [Symbol] name the name of the attribute to define.
263
- # @param [Class] type (defaults to Ohm::Types::String) a class defined in
267
+ # @param [Class] type (defaults to Ohm::Types::String) a class defined in
264
268
  # Ohm::Types. You may define custom types in Ohm::Types if
265
269
  # you need to.
266
270
  # @return [Array] the array of attributes already defined.
@@ -269,8 +273,8 @@ module Ohm
269
273
  define_method(name) do
270
274
  # Primitive types maintain a reference to the original object
271
275
  # stored in @_attributes[att]. Hence mutation works for the
272
- # Primitive case. For cases like Hash, Array where the value
273
- # is `JSON.parse`d, we need to set the actual Ohm::Types::Hash
276
+ # Primitive case. For cases like Hash, Array where the value
277
+ # is `JSON.parse`d, we need to set the actual Ohm::Types::Hash
274
278
  # (or similar) to @_attributes[att] for mutation to work.
275
279
  if klass.superclass == Ohm::Types::Primitive
276
280
  klass[read_local(name)]
@@ -296,4 +300,4 @@ module Ohm
296
300
  end
297
301
  end
298
302
  end
299
- end
303
+ end
@@ -12,9 +12,9 @@ module Ohm
12
12
  module WebValidations
13
13
  # @see http://fightingforalostcause.net/misc/2006/compare-email-regex.php
14
14
  EMAIL_REGEX = /^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i
15
-
15
+
16
16
  SLUG_REGEX = /^[-\w]+$/
17
-
17
+
18
18
  URL_REGEX = /^(http|https):\/\/([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}|(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|localhost)(:[0-9]{1,5})?(\/.*)?$/ix
19
19
 
20
20
  IPV4_REGEX = /^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$/
@@ -48,4 +48,4 @@ module Ohm
48
48
  assert_ipv4(att, error)
49
49
  end
50
50
  end
51
- end
51
+ end
data/ohm-contrib.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ohm-contrib}
8
- s.version = "0.0.20"
8
+ s.version = "0.0.21"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Cyril David"]
12
- s.date = %q{2010-05-29}
12
+ s.date = %q{2010-05-30}
13
13
  s.description = %q{Highly decoupled drop-in functionality for Ohm models}
14
14
  s.email = %q{cyx.ucron@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -14,7 +14,7 @@ class OhmContribCallbacksTest < Test::Unit::TestCase
14
14
 
15
15
  before :save, :do_before_save
16
16
  after :save, :do_after_save
17
-
17
+
18
18
  before :delete, :do_before_delete
19
19
  after :delete, :do_after_delete
20
20
 
@@ -151,8 +151,8 @@ class OhmContribCallbacksTest < Test::Unit::TestCase
151
151
  @post = Post[@post.id]
152
152
  @post.delete
153
153
  end
154
-
155
-
154
+
155
+
156
156
  should "call delete related callbacks once" do
157
157
  assert_equal 1, @post.count(:do_before_delete)
158
158
  assert_equal 1, @post.count(:do_after_delete)
@@ -167,4 +167,4 @@ class OhmContribCallbacksTest < Test::Unit::TestCase
167
167
  assert ! @post.did?(:do_after_save)
168
168
  end
169
169
  end
170
- end
170
+ end
@@ -92,14 +92,23 @@ class TestOhmTypecast < Test::Unit::TestCase
92
92
  assert_kind_of String, post.price.to_s
93
93
  end
94
94
 
95
- test "equality matching" do
95
+ test "equality and comparable matching" do
96
96
  post = Post.create(:price => "399.50")
97
97
  assert (post.price == "399.50")
98
+ assert (post.price < 399.51)
99
+ assert (post.price > 399.49)
100
+ assert (post.price <= 399.50)
101
+ assert (post.price <= 399.51)
102
+ assert (post.price >= 399.50)
103
+ assert (post.price >= 399.49)
98
104
  end
99
105
 
100
106
  test "inspecting a Decimal" do
101
107
  post = Post.new(:price => 399.50)
102
- assert_equal '399.5', post.price.inspect
108
+ assert_equal '"399.5"', post.price.inspect
109
+
110
+ post.price = 'FooBar'
111
+ assert_equal '"FooBar"', post.price.inspect
103
112
  end
104
113
  end
105
114
 
@@ -145,7 +154,10 @@ class TestOhmTypecast < Test::Unit::TestCase
145
154
 
146
155
  test "inspecting" do
147
156
  post = Post.new(:price => "50000")
148
- assert_equal '50000', post.price.inspect
157
+ assert_equal '"50000"', post.price.inspect
158
+
159
+ post.price = 'FooBar'
160
+ assert_equal '"FooBar"', post.price.inspect
149
161
  end
150
162
  end
151
163
 
@@ -191,7 +203,10 @@ class TestOhmTypecast < Test::Unit::TestCase
191
203
 
192
204
  test "inspecting" do
193
205
  post = Post.new(:price => "12345.67890")
194
- assert_equal '12345.6789', post.price.inspect
206
+ assert_equal '"12345.67890"', post.price.inspect
207
+
208
+ post.price = 'FooBar'
209
+ assert_equal '"FooBar"', post.price.inspect
195
210
  end
196
211
  end
197
212
 
@@ -250,6 +265,14 @@ class TestOhmTypecast < Test::Unit::TestCase
250
265
  post.created_at.slice
251
266
  end
252
267
  end
268
+
269
+ test "inspecting" do
270
+ post = Post.create(:created_at => Time.utc(2010, 05, 05))
271
+ assert_equal '"2010-05-05 00:00:00 UTC"', post.created_at.inspect
272
+
273
+ post.created_at = 'FooBar'
274
+ assert_equal '"FooBar"', post.created_at.inspect
275
+ end
253
276
  end
254
277
 
255
278
  context "when using a date" do
@@ -317,6 +340,14 @@ class TestOhmTypecast < Test::Unit::TestCase
317
340
  test "still able to access Date" do
318
341
  assert_equal Date.today, Post.new.today
319
342
  end
343
+
344
+ test "inspecting" do
345
+ post = Post.create(:created_on => Date.new(2010, 5, 5))
346
+ assert_equal '"2010-05-05"', post.created_on.inspect
347
+
348
+ post.created_on = 'FooBar'
349
+ assert_equal '"FooBar"', post.created_on.inspect
350
+ end
320
351
  end
321
352
 
322
353
  context "when using a Hash" do
@@ -333,13 +364,13 @@ class TestOhmTypecast < Test::Unit::TestCase
333
364
  Hash
334
365
  end
335
366
  end
336
-
367
+
337
368
  test "importing" do
338
369
  assert_equal Hash.new, Ohm::Types::Hash[nil]
339
370
  assert_equal Hash.new, Ohm::Types::Hash[""]
340
371
  assert_equal Hash.new, Ohm::Types::Hash[{}]
341
372
 
342
- assert_equal Hash[:a => "b", :c => "d"],
373
+ assert_equal Hash[:a => "b", :c => "d"],
343
374
  Ohm::Types::Hash[{ :a => "b", :c => "d" }]
344
375
  end
345
376
 
@@ -360,7 +391,7 @@ class TestOhmTypecast < Test::Unit::TestCase
360
391
  test "handles nil case correctly" do
361
392
  post = Post.create(:address => nil)
362
393
  assert_equal({}, post.address)
363
-
394
+
364
395
  post = Post[post.id]
365
396
  assert_equal({}, post.address)
366
397
  end
@@ -368,7 +399,7 @@ class TestOhmTypecast < Test::Unit::TestCase
368
399
  test "handles empty string case correctly" do
369
400
  post = Post.create(:address => "")
370
401
  assert_equal({}, post.address)
371
-
402
+
372
403
  post = Post[post.id]
373
404
  assert_equal({}, post.address)
374
405
  end
@@ -377,7 +408,7 @@ class TestOhmTypecast < Test::Unit::TestCase
377
408
  address = { "address1" => "#123", "city" => "Singapore", "country" => "SG"}
378
409
  post = Post.create(:address => address)
379
410
  assert_equal address, post.address
380
-
411
+
381
412
  post = Post[post.id]
382
413
  assert_equal address, post.address
383
414
  end
@@ -385,7 +416,7 @@ class TestOhmTypecast < Test::Unit::TestCase
385
416
  test "allows for hash operations" do
386
417
  address = { "address1" => "#123", "city" => "Singapore", "country" => "SG"}
387
418
  post = Post.create(:address => address)
388
-
419
+
389
420
  assert_equal ["address1", "city", "country"], post.address.keys
390
421
  assert_equal ["#123", "Singapore", "SG"], post.address.values
391
422
 
@@ -397,7 +428,7 @@ class TestOhmTypecast < Test::Unit::TestCase
397
428
  test "handles mutation" do
398
429
  address = { "address1" => "#123", "city" => "Singapore", "country" => "SG"}
399
430
  post = Post.create(:address => address)
400
-
431
+
401
432
  post.address["address1"] = "#456"
402
433
  post.save
403
434
 
@@ -408,7 +439,7 @@ class TestOhmTypecast < Test::Unit::TestCase
408
439
  assert_equal ["address1", "city", "country"], post.address.keys
409
440
  assert_equal ["#456", "Singapore", "SG"], post.address.values
410
441
  end
411
-
442
+
412
443
  Address = Class.new(Struct.new(:city, :country))
413
444
 
414
445
  test "raises when trying to assign a non-hash" do
@@ -420,6 +451,18 @@ class TestOhmTypecast < Test::Unit::TestCase
420
451
  Post.new(:address => Address.new)
421
452
  end
422
453
  end
454
+
455
+ test "inspecting" do
456
+ post = Post.create(:address => { "address1" => "#456",
457
+ "city" => "Singapore",
458
+ "country" => "SG" })
459
+
460
+ assert_equal %q{{"address1":"#456","city":"Singapore","country":"SG"}},
461
+ post.address.inspect
462
+
463
+ post.address = 'FooBar'
464
+ assert_equal %{"\\\"FooBar\\\""}, post.address.inspect
465
+ end
423
466
  end
424
467
 
425
468
  context "when using an Array" do
@@ -436,7 +479,7 @@ class TestOhmTypecast < Test::Unit::TestCase
436
479
  Array
437
480
  end
438
481
  end
439
-
482
+
440
483
  test "importing" do
441
484
  assert_equal [], Ohm::Types::Array[nil]
442
485
  assert_equal [], Ohm::Types::Array[""]
@@ -463,7 +506,7 @@ class TestOhmTypecast < Test::Unit::TestCase
463
506
  test "handles nil case correctly" do
464
507
  post = Post.create(:addresses => nil)
465
508
  assert_equal([], post.addresses)
466
-
509
+
467
510
  post = Post[post.id]
468
511
  assert_equal([], post.addresses)
469
512
  end
@@ -471,7 +514,7 @@ class TestOhmTypecast < Test::Unit::TestCase
471
514
  test "handles empty string case correctly" do
472
515
  post = Post.create(:addresses => "")
473
516
  assert_equal([], post.addresses)
474
-
517
+
475
518
  post = Post[post.id]
476
519
  assert_equal([], post.addresses)
477
520
  end
@@ -482,11 +525,11 @@ class TestOhmTypecast < Test::Unit::TestCase
482
525
 
483
526
  post = Post.create(:addresses => addresses)
484
527
  assert_equal addresses, post.addresses
485
-
528
+
486
529
  post = Post[post.id]
487
530
  assert_equal addresses, post.addresses
488
531
  end
489
-
532
+
490
533
  class Address < Struct.new(:city, :country)
491
534
  def to_json
492
535
  [city, country].to_json
@@ -496,10 +539,10 @@ class TestOhmTypecast < Test::Unit::TestCase
496
539
  test "handles an arbitrary class as an element of the array" do
497
540
  addresses = [Address.new("Singapore", "SG"),
498
541
  Address.new("Philippines", "PH")]
499
-
542
+
500
543
  post = Post.create(:addresses => addresses)
501
544
  assert_equal [['Singapore', 'SG'], ['Philippines', 'PH']], post.addresses
502
-
545
+
503
546
  post = Post[post.id]
504
547
  assert_equal [['Singapore', 'SG'], ['Philippines', 'PH']], post.addresses
505
548
  end
@@ -520,9 +563,23 @@ class TestOhmTypecast < Test::Unit::TestCase
520
563
  post.addresses.push({"city" => "Hong Kong", "country" => "ZN"})
521
564
  end
522
565
 
566
+ test "looping! and other enumerablems" do
567
+ array = [1, 2, 3]
568
+ post = Post.create(:addresses => array)
569
+
570
+ total = 0
571
+ post.addresses.each { |e| total += e }
572
+ assert_equal 6, total
573
+
574
+ post = Post[post.id]
575
+ total = 0
576
+ post.addresses.each { |e| total += e }
577
+ assert_equal 6, total
578
+ end
579
+
523
580
  test "handles mutation" do
524
581
  post = Post.create(:addresses => [1, 2, 3])
525
-
582
+
526
583
  post.addresses.push(4, 5, 6)
527
584
  post.save
528
585
 
@@ -533,7 +590,7 @@ class TestOhmTypecast < Test::Unit::TestCase
533
590
  assert_equal 6, post.addresses.size
534
591
  assert_equal [1, 2, 3, 4, 5, 6], post.addresses
535
592
  end
536
-
593
+
537
594
 
538
595
  test "raises when trying to assign a non-array" do
539
596
  assert_raise TypeError do
@@ -544,5 +601,17 @@ class TestOhmTypecast < Test::Unit::TestCase
544
601
  Post.new(:addresses => Address.new)
545
602
  end
546
603
  end
604
+
605
+ test "inspecting" do
606
+ post = Post.create(:addresses => [{ "address1" => "#456",
607
+ "city" => "Singapore",
608
+ "country" => "SG" }])
609
+
610
+ assert_equal %q{[{"address1":"#456","city":"Singapore","country":"SG"}]},
611
+ post.addresses.inspect
612
+
613
+ post.addresses = 'FooBar'
614
+ assert_equal %{"\\\"FooBar\\\""}, post.addresses.inspect
615
+ end
547
616
  end
548
- end
617
+ end
@@ -111,4 +111,4 @@ class TestOhmWebValidations < Test::Unit::TestCase
111
111
  end
112
112
 
113
113
  end
114
- end
114
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 20
9
- version: 0.0.20
8
+ - 21
9
+ version: 0.0.21
10
10
  platform: ruby
11
11
  authors:
12
12
  - Cyril David
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-29 00:00:00 +08:00
17
+ date: 2010-05-30 00:00:00 +08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency