ohm 2.0.0.alpha5 → 2.0.0.rc1

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: eddfa837c7904b72acba3e95ea9af454412bef88
4
- data.tar.gz: de756a4ef37ea55738cea7636a251a411da4cabd
3
+ metadata.gz: 60d1fadf47380356ffbc44627e28f9b15ff8528a
4
+ data.tar.gz: f911d9c462235f7c56f5216b10f1b2d48f3ab287
5
5
  SHA512:
6
- metadata.gz: cc35a4e810775b5350ccd255035b2659b5559aa277d91a0f4584bf417bf663d4d27173d64ef324c7d305aba6eda8cc1f96bfde9ab71ead4f237b7382613cad45
7
- data.tar.gz: 6314ece218f9ad0f82548dfb99b44db51979d14e8282277ae1abb9f4dfba5c96a68cadd8ec573efe6d475aeb0315b33aded1ce26862c28b3ff8cc6be8d761bfb
6
+ metadata.gz: 67eea41a25fd17691a9cc79700abf248438a7bccefc7624053212d6439d6252bfc2850e0a7905fe8a2ad46eadac6d9d11d150978cec4f937a2bbba89ea05e90b
7
+ data.tar.gz: 1e17aace64652f6c96cd79fa6ff071a67fbed614951af15e8487d8fee78b21ec69f10313b7e8b1004292d1f97a22c3d8be74d5f892172742fe634fea84989ef2
data/.gitignore CHANGED
@@ -1,4 +1,3 @@
1
- /pkg
2
1
  /.yardoc
3
2
  /doc
4
- .rbenv-gemsets
3
+ /.gs
data/CHANGELOG CHANGED
@@ -1,3 +1,63 @@
1
+ (unreleased)
2
+
3
+ - Include Ohm::BasicSet#exists? in the public API. This makes possible
4
+ to check if an id is included in a set. Check Ohm::BasicSet#exists?
5
+ documentation for more details.
6
+
7
+ - Change Ohm::MultiSet#except to union keys instead of intersect them
8
+ when passing an array.
9
+
10
+ class User < Ohm::Model
11
+ attribute :name
12
+ end
13
+
14
+ john = User.create(:name => "John")
15
+ jane = User.create(:name => "Jane")
16
+
17
+ res = User.all.except(:name => [john.name, jane.name])
18
+
19
+ # before
20
+ res.size # => 2
21
+
22
+ # now
23
+ res.size # => 0
24
+
25
+ - Move ID generation to Lua. With this change, it's no longer possible
26
+ to generate custom ids. All ids are autoincremented.
27
+
28
+ - Ohm::Model#reference accepts strings as model references. For example:
29
+
30
+ class Bar < Ohm::Model
31
+ reference :foo, "SomeNamespace::Foo"
32
+ end
33
+
34
+ - Nest dependency has been removed. Now, Ohm uses Nido
35
+ (https://github.com/soveran/nido) to generate the keys that hold
36
+ the data.
37
+
38
+ - Scrivener dependency has been removed.
39
+
40
+ - Ohm no longer supports model validations and favors filter validation
41
+ on the boundary layer. Check Scrivener project
42
+ (https://github.com/soveran/scrivener) for more information.
43
+
44
+ - Redis dependency has been removed. Now, Ohm uses Redic
45
+ (https://github.com/amakawa/redic), a lightweight Redis client.
46
+ Redic uses the hiredis gem for the connection and for parsing
47
+ the replies. Check Redic's README for more details.
48
+
49
+ - Ohm::Model#transaction has been removed.
50
+
51
+ - Ohm::Transaction has been removed.
52
+
53
+ 1.3.2
54
+
55
+ - Fetching a batch of objects is now done in batches of 1000 objects at
56
+ a time. If you are iterating over large collections, this change should
57
+ provide a significant performance boost both in used memory and total
58
+ execution time.
59
+ - MutableSet#<< is now an alias for #add.
60
+
1
61
  1.3.1
2
62
 
3
63
  - Improve memory consumption when indexing persisted attributes.
data/README.md CHANGED
@@ -7,8 +7,7 @@ Description
7
7
  -----------
8
8
 
9
9
  Ohm is a library for storing objects in [Redis][redis], a persistent key-value
10
- database. It includes an extensible list of validations and has very good
11
- performance.
10
+ database. It has very good performance.
12
11
 
13
12
  Community
14
13
  ---------
@@ -17,7 +16,6 @@ Join the mailing list: [http://groups.google.com/group/ohm-ruby](http://groups.g
17
16
 
18
17
  Meet us on IRC: [#ohm](irc://chat.freenode.net/#ohm) on [freenode.net](http://freenode.net/)
19
18
 
20
-
21
19
  Related projects
22
20
  ----------------
23
21
 
@@ -58,61 +56,55 @@ Now, in an irb session you can test the Redis adapter directly:
58
56
 
59
57
  >> require "ohm"
60
58
  => true
61
- >> Ohm.connect
62
- => []
63
- >> Ohm.redis.set "Foo", "Bar"
59
+ >> Ohm.redis.call "SET", "Foo", "Bar"
64
60
  => "OK"
65
- >> Ohm.redis.get "Foo"
61
+ >> Ohm.redis.call "GET", "Foo"
66
62
  => "Bar"
67
63
 
68
- ## Connecting to the Redis database
69
-
70
- There are a couple of different strategies for connecting to your Redis
71
- database. The first is to explicitly set the `:host`, `:port`, `:db` and
72
- `:timeout` options. You can also set only a few of them, and let the other
73
- options fall back to the default.
74
-
75
- The other noteworthy style of connecting is by just doing `Ohm.connect` and
76
- set the environment variable `REDIS_URL`.
64
+ ## Connecting to a Redis database
77
65
 
78
- Here are the options for {Ohm.connect} in detail:
66
+ Ohm uses a lightweight Redis client called [Redic][redic]. To connect
67
+ to a Redis database, you will need to set an instance of `Redic`, with
68
+ an URL of the form `redis://:<passwd>@<host>:<port>/<db>`, through the
69
+ `Ohm.redis=` method, e.g.
79
70
 
80
- ### :url (recommended)
81
-
82
- A Redis URL of the form `redis://:<passwd>@<host>:<port>/<db>`.
83
- Note that if you specify a URL and one of the other options at
84
- the same time, the other options will take precedence. Also, if
85
- you try and do `Ohm.connect` without any arguments, it will check
86
- if `ENV["REDIS_URL"]` is set, and will use it as the argument for
87
- `:url`.
71
+ ```ruby
72
+ require "ohm"
88
73
 
89
- ### :host
74
+ Ohm.redis = Redic.new("redis://127.0.0.1:6379")
90
75
 
91
- Host where the Redis server is running, defaults to `"127.0.0.1"`.
76
+ Ohm.redis.call "SET", "Foo", "Bar"
92
77
 
93
- ### :port
78
+ Ohm.redis.call "GET", "Foo"
79
+ # => "Bar"
80
+ ```
94
81
 
95
- Port number, defaults to `6379`.
82
+ Ohm defaults to a Redic connection to "redis://127.0.0.1:6379". The
83
+ example above could be rewritten as:
96
84
 
97
- ### :db
85
+ ```ruby
86
+ require "ohm"
98
87
 
99
- Database number, defaults to `0`.
88
+ Ohm.redis.call "SET", "Foo", "Bar"
100
89
 
101
- ### :password
90
+ Ohm.redis.call "GET", "Foo"
91
+ # => "Bar"
92
+ ```
102
93
 
103
- It is the secret that will be sent to the Redis server. Use it if the server
104
- configuration requires it. Defaults to `nil`.
94
+ All Ohm models inherit the same connection settings from `Ohm.redis`.
95
+ For cases where certain models need to connect to different databases,
96
+ they simple have to override that, i.e.
105
97
 
106
- ### :timeout
98
+ ```ruby
99
+ require "ohm"
107
100
 
108
- Database timeout in seconds, defaults to `0`.
101
+ Ohm.redis = Redic.new(ENV["REDIS_URL1"])
109
102
 
110
- ### :thread_safe
103
+ class User < Ohm::Model
104
+ end
111
105
 
112
- Initializes the client with a monitor. It has a small performance penalty, and
113
- it's off by default. For thread safety, it is recommended to use a different
114
- instance per thread. I you have no choice, then pass `:thread_safe => true`
115
- when connecting.
106
+ User.redis = Redic.new(ENV["REDIS_URL2"])
107
+ ```
116
108
 
117
109
  Models
118
110
  ------
@@ -131,10 +123,6 @@ class Event < Ohm::Model
131
123
  counter :votes
132
124
 
133
125
  index :name
134
-
135
- def validate
136
- assert_present :name
137
- end
138
126
  end
139
127
 
140
128
  class Venue < Ohm::Model
@@ -165,12 +153,12 @@ Event[2]
165
153
  # => nil
166
154
 
167
155
  # Finding all the events
168
- Event.all
169
- # => [#<Event @values={:id=>1, :name=>"Ohm Worldwide Conference 2031"}>]
156
+ Event.all.to_a
157
+ # => [<Event:1 name='Ohm Worldwide Conference 2031'>]
170
158
  ```
171
159
 
172
160
  This example shows some basic features, like attribute declarations and
173
- validations. Keep reading to find out what you can do with models.
161
+ querying. Keep reading to find out what you can do with models.
174
162
 
175
163
  Attribute types
176
164
  ---------------
@@ -223,31 +211,20 @@ Persistence strategy
223
211
  --------------------
224
212
 
225
213
  The attributes declared with `attribute` are only persisted after
226
- calling `save`. If the object is in an invalid state, no value is sent
227
- to Redis (see the section on **Validations** below).
214
+ calling `save`.
228
215
 
229
216
  Operations on attributes of type `list`, `set` and `counter` are
230
217
  possible only after the object is created (when it has an assigned
231
218
  `id`). Any operation on these kinds of attributes is performed
232
- immediately, without running the object validations. This design yields
233
- better performance than running the validations on each operation or
234
- buffering the operations and waiting for a call to `save`.
219
+ immediately. This design yields better performance than buffering
220
+ the operations and waiting for a call to `save`.
235
221
 
236
222
  For most use cases, this pattern doesn't represent a problem.
237
- If you need to check for validity before operating on lists, sets or
238
- counters, you can use this pattern:
239
-
240
- ```ruby
241
- if event.valid?
242
- event.comments.add(Comment.create(:body => "Great event!"))
243
- end
244
- ```
245
-
246
223
  If you are saving the object, this will suffice:
247
224
 
248
225
  ```ruby
249
226
  if event.save
250
- event.comments.add(Comment.create(:body => "Wonderful event!"))
227
+ event.comments.add(Comment.create(body: "Wonderful event!"))
251
228
  end
252
229
  ```
253
230
 
@@ -267,7 +244,7 @@ You can add instances of `Person` to the set of attendees with the
267
244
  `add` method:
268
245
 
269
246
  ```ruby
270
- event.attendees.add(Person.create(:name => "Albert"))
247
+ event.attendees.add(Person.create(name: "Albert"))
271
248
 
272
249
  # And now...
273
250
  event.attendees.each do |person|
@@ -317,7 +294,7 @@ and it's within the current model you are sorting.
317
294
 
318
295
  ```ruby
319
296
  Post.all.sort_by(:title) # SORT Post:all BY Post:*->title
320
- Post.all.sort(:by => :title) # SORT Post:all BY title
297
+ Post.all.sort(by: :title) # SORT Post:all BY title
321
298
  ```
322
299
 
323
300
  __Tip:__ Unless you absolutely know what you're doing, use `sort`
@@ -332,10 +309,10 @@ the `:by` option, using {Ohm::Model::Collection#sort sort} and
332
309
  that `sort_by` does much of the hand-coding for you.
333
310
 
334
311
  ```ruby
335
- Post.all.sort_by(:title, :get => :title)
312
+ Post.all.sort_by(:title, get: :title)
336
313
  # SORT Post:all BY Post:*->title GET Post:*->title
337
314
 
338
- Post.all.sort(:by => :title, :get => :title)
315
+ Post.all.sort(by: :title, get: :title)
339
316
  # SORT Post:all BY title GET title
340
317
  ```
341
318
 
@@ -360,7 +337,7 @@ end
360
337
 
361
338
  After this, every time you refer to `post.comments` you will be talking
362
339
  about instances of the model `Comment`. If you want to get a list of IDs
363
- you can use `post.comments.key.smembers`.
340
+ you can use `post.comments.ids`.
364
341
 
365
342
  ### References explained
366
343
 
@@ -391,7 +368,7 @@ The net effect here is we can conveniently set and retrieve `Post` objects,
391
368
  and also search comments using the `post_id` index.
392
369
 
393
370
  ```ruby
394
- Comment.find(:post_id => 1)
371
+ Comment.find(post_id: 1)
395
372
  ```
396
373
 
397
374
  ### Collections explained
@@ -409,7 +386,7 @@ class Post < Ohm::Model
409
386
  attribute :body
410
387
 
411
388
  def comments
412
- Comment.find(:post_id => self.id)
389
+ Comment.find(post_id: self.id)
413
390
  end
414
391
  end
415
392
  ```
@@ -441,7 +418,7 @@ any index declared, Ohm maintains different sets of objects IDs for quick
441
418
  lookups.
442
419
 
443
420
  In the `Event` example, the index on the name attribute will
444
- allow for searches like `Event.find(:name => "some value")`.
421
+ allow for searches like `Event.find(name: "some value")`.
445
422
 
446
423
  Note that the methods {Ohm::Model::Set#find find} and
447
424
  {Ohm::Model::Set#except except} need a corresponding index in order to work.
@@ -452,23 +429,23 @@ You can find a collection of records with the `find` method:
452
429
 
453
430
  ```ruby
454
431
  # This returns a collection of users with the username "Albert"
455
- User.find(:username => "Albert")
432
+ User.find(username: "Albert")
456
433
  ```
457
434
 
458
435
  ### Filtering results
459
436
 
460
437
  ```ruby
461
438
  # Find all users from Argentina
462
- User.find(:country => "Argentina")
439
+ User.find(country: "Argentina")
463
440
 
464
441
  # Find all activated users from Argentina
465
- User.find(:country => "Argentina", :status => "activated")
442
+ User.find(country: "Argentina", status: "activated")
466
443
 
467
444
  # Find all users from Argentina, except those with a suspended account.
468
- User.find(:country => "Argentina").except(:status => "suspended")
445
+ User.find(country: "Argentina").except(status: "suspended")
469
446
 
470
447
  # Find all users both from Argentina and Uruguay
471
- User.find(:country => "Argentina").union(:country => "Uruguay")
448
+ User.find(country: "Argentina").union(country: "Uruguay")
472
449
  ```
473
450
 
474
451
  Note that calling these methods results in new sets being created
@@ -499,131 +476,6 @@ User.create(email: "foo@bar.com")
499
476
  # => raises Ohm::UniqueIndexViolation
500
477
  ```
501
478
 
502
- Validations
503
- -----------
504
-
505
- Before every save, the `validate` method is called by Ohm. In the method
506
- definition you can use assertions that will determine if the attributes
507
- are valid. Nesting assertions is a good practice, and you are also
508
- encouraged to create your own assertions. You can trigger validations at
509
- any point by calling `valid?` on a model instance.
510
-
511
- Assertions
512
- -----------
513
-
514
- Ohm ships with some basic assertions. Check Ohm::Validations to see
515
- the method definitions.
516
-
517
- ### assert
518
-
519
- The `assert` method is used by all the other assertions. It pushes the
520
- second parameter to the list of errors if the first parameter evaluates
521
- to false.
522
-
523
- ```ruby
524
- def assert(value, error)
525
- value or errors.push(error) && false
526
- end
527
- ```
528
-
529
- ### assert_present
530
-
531
- Checks that the given field is not nil or empty. The error code for this
532
- assertion is `:not_present`.
533
-
534
- ```ruby
535
- assert_present :name
536
- ```
537
-
538
- ### assert_format
539
-
540
- Checks that the given field matches the provided format. The error code
541
- for this assertion is :format.
542
-
543
- ```ruby
544
- assert_format :username, /^\w+$/
545
- ```
546
-
547
- ### assert_numeric
548
-
549
- Checks that the given field holds a number as a Fixnum or as a string
550
- representation. The error code for this assertion is :not_numeric.
551
-
552
- ```ruby
553
- assert_numeric :votes
554
- ```
555
-
556
- ### assert_url
557
-
558
- Provides a pretty general URL regular expression match. An important
559
- point to make is that this assumes that the URL should start with
560
- `http://` or `https://`. The error code for this assertion is
561
- `:not_url`.
562
-
563
- ### assert_email
564
-
565
- In this current day and age, almost all web applications need to
566
- validate an email address. This pretty much matches 99% of the emails
567
- out there. The error code for this assertion is `:not_email`.
568
-
569
- ### assert_member
570
-
571
- Checks that a given field is contained within a set of values (i.e.
572
- like an `ENUM`).
573
-
574
- ``` ruby
575
- def validate
576
- assert_member :state, %w{pending paid delivered}
577
- end
578
- ```
579
-
580
- The error code for this assertion is `:not_valid`
581
-
582
- ### assert_length
583
-
584
- Checks that a given field's length falls under a specified range.
585
-
586
- ``` ruby
587
- def validate
588
- assert_length :username, 3..20
589
- end
590
- ```
591
-
592
- The error code for this assertion is `:not_in_range`.
593
-
594
- ### assert_decimal
595
-
596
- Checks that a given field looks like a number in the human sense
597
- of the word. Valid numbers are: 0.1, .1, 1, 1.1, 3.14159, etc.
598
-
599
- The error code for this assertion is `:not_decimal`.
600
-
601
- Errors
602
- ------
603
-
604
- When an assertion fails, the error report is added to the errors array.
605
- Each error report contains two elements: the field where the assertion
606
- was issued and the error code.
607
-
608
- ### Validation example
609
-
610
- Given the following example:
611
-
612
- ```ruby
613
- def validate
614
- assert_present :foo
615
- assert_numeric :bar
616
- assert_format :baz, /^\d{2}$/
617
- end
618
- ```
619
-
620
- If all the assertions fail, the following errors will be present:
621
-
622
- ```ruby
623
- obj.errors
624
- # => { foo: [:not_present], bar: [:not_numeric], baz: [:format] }
625
- ```
626
-
627
479
  Ohm Extensions
628
480
  ==============
629
481
 
@@ -637,10 +489,10 @@ make sure to check them if you need to extend Ohm's functionality.
637
489
  Upgrading
638
490
  =========
639
491
 
640
- The changes in Ohm 1 break the compatibility with previous versions.
641
- We will do our best to provide a script to ease the pain of upgrading.
642
- In the meantime, it's recommended that you use the new version only
643
- for new projects.
492
+ Ohm 2 breaks the compatibility with previous versions. If you're upgrading an
493
+ existing application, it's nice to have a good test coverage before going in.
494
+ To know about fixes and changes, please refer to the CHANGELOG file.
644
495
 
645
496
  [redis]: http://redis.io
646
497
  [ohm]: http://github.com/soveran/ohm
498
+ [redic]: https://github.com/amakawa/redic
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- require "rake/testtask"
2
-
3
1
  REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
4
2
  REDIS_CNF = File.join(REDIS_DIR, "test.conf")
5
3
  REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
data/lib/ohm.rb CHANGED
@@ -56,11 +56,12 @@ module Ohm
56
56
  # reference :user, User # NameError undefined constant User.
57
57
  # end
58
58
  #
59
- # Instead of relying on some clever `const_missing` hack, we can
60
- # simply use a Symbol.
59
+ # # Instead of relying on some clever `const_missing` hack, we can
60
+ # # simply use a symbol or a string.
61
61
  #
62
62
  # class Comment < Ohm::Model
63
63
  # reference :user, :User
64
+ # reference :post, "Post"
64
65
  # end
65
66
  #
66
67
  def self.const(context, name)
@@ -166,7 +167,6 @@ module Ohm
166
167
  def size
167
168
  redis.call("LLEN", key)
168
169
  end
169
- alias :count :size
170
170
 
171
171
  # Returns the first element of the list using LINDEX.
172
172
  def first
@@ -342,7 +342,6 @@ module Ohm
342
342
  def size
343
343
  execute { |key| redis.call("SCARD", key) }
344
344
  end
345
- alias :count :size
346
345
 
347
346
  # Syntactic sugar for `sort_by` or `sort` when you only need the
348
347
  # first element.
@@ -385,11 +384,29 @@ module Ohm
385
384
  model[id] if exists?(id)
386
385
  end
387
386
 
388
- private
387
+ # Returns +true+ if +id+ is included in the set. Otherwise, returns +false+.
388
+ #
389
+ # Example:
390
+ #
391
+ # class Post < Ohm::Model
392
+ # end
393
+ #
394
+ # class User < Ohm::Model
395
+ # set :posts, :Post
396
+ # end
397
+ #
398
+ # user = User.create
399
+ # post = Post.create
400
+ # user.posts.add(post)
401
+ #
402
+ # user.posts.exists?('nonexistent') # => false
403
+ # user.posts.exists?(post.id) # => true
404
+ #
389
405
  def exists?(id)
390
406
  execute { |key| redis.call("SISMEMBER", key, id) == 1 }
391
407
  end
392
408
 
409
+ private
393
410
  def to_key(att)
394
411
  if model.counters.include?(att)
395
412
  namespace["*:counters->%s" % att]
@@ -569,7 +586,7 @@ module Ohm
569
586
  #
570
587
  def except(dict)
571
588
  MultiSet.new(
572
- namespace, model, Command[:sdiffstore, command, intersected(dict)]
589
+ namespace, model, Command[:sdiffstore, command, unioned(dict)]
573
590
  )
574
591
  end
575
592
 
@@ -598,6 +615,10 @@ module Ohm
598
615
  Command[:sinterstore, *model.filters(dict)]
599
616
  end
600
617
 
618
+ def unioned(dict)
619
+ Command[:sunionstore, *model.filters(dict)]
620
+ end
621
+
601
622
  def execute
602
623
  # namespace[:tmp] is where all the temp keys should be stored in.
603
624
  # redis will be where all the commands are executed against.
@@ -677,7 +698,7 @@ module Ohm
677
698
  @redis ||= Redic.new(Ohm.redis.url)
678
699
  end
679
700
 
680
- # The namespace for all the keys generated using this model.
701
+ # Returns the namespace for all the keys generated using this model.
681
702
  #
682
703
  # Example:
683
704
  #
@@ -1018,7 +1039,7 @@ module Ohm
1018
1039
  # u = User.create
1019
1040
  # u.incr :points
1020
1041
  #
1021
- # Ohm.redis.hget "User:1:counters", "points"
1042
+ # u.points
1022
1043
  # # => 1
1023
1044
  #
1024
1045
  # Note: You can't use counters until you save the model. If you
@@ -1049,21 +1070,8 @@ module Ohm
1049
1070
  new(atts).save
1050
1071
  end
1051
1072
 
1052
- # Manipulate the Redis hash of attributes directly.
1053
- #
1054
- # Example:
1055
- #
1056
- # class User < Ohm::Model
1057
- # attribute :name
1058
- # end
1059
- #
1060
- # u = User.create(:name => "John")
1061
- # u.key.hget(:name)
1062
- # # => John
1063
- #
1064
- # For more details see
1065
- # http://github.com/soveran/nest
1066
- #
1073
+ # Returns the namespace for the keys generated using this model.
1074
+ # Check `Ohm::Model.key` documentation for more details.
1067
1075
  def key
1068
1076
  model.key[id]
1069
1077
  end
@@ -1153,6 +1161,21 @@ module Ohm
1153
1161
  @attributes[att] = val
1154
1162
  end
1155
1163
 
1164
+ # Returns +true+ if the model is not persisted. Otherwise, returns +false+.
1165
+ #
1166
+ # Example:
1167
+ #
1168
+ # class User < Ohm::Model
1169
+ # attribute :name
1170
+ # end
1171
+ #
1172
+ # u = User.new(:name => "John")
1173
+ # u.new?
1174
+ # # => true
1175
+ #
1176
+ # u.save
1177
+ # u.new?
1178
+ # # => false
1156
1179
  def new?
1157
1180
  !defined?(@id)
1158
1181
  end
@@ -1184,6 +1207,20 @@ module Ohm
1184
1207
  end
1185
1208
  alias :eql? :==
1186
1209
 
1210
+ # Returns a hash of the attributes with their names as keys
1211
+ # and the values of the attributes as values. It doesn't
1212
+ # include the ID of the model.
1213
+ #
1214
+ # Example:
1215
+ #
1216
+ # class User < Ohm::Model
1217
+ # attribute :name
1218
+ # end
1219
+ #
1220
+ # u = User.create(:name => "John")
1221
+ # u.attributes
1222
+ # # => { :name => "John" }
1223
+ #
1187
1224
  def attributes
1188
1225
  @attributes
1189
1226
  end
@@ -14,11 +14,4 @@ module Ohm
14
14
  to_a.to_json(*args)
15
15
  end
16
16
  end
17
-
18
- class List
19
- # Sugar for to_a.to_json for lists.
20
- def to_json(*args)
21
- to_a.to_json(*args)
22
- end
23
- end
24
17
  end
@@ -6,10 +6,11 @@
6
6
  --
7
7
  -- # model
8
8
  --
9
- -- Table with three attributes:
10
- -- id (model instance id)
11
- -- key (hash where the attributes will be saved)
9
+ -- Table with one or two attributes:
12
10
  -- name (model name)
11
+ -- id (model instance id, optional)
12
+ --
13
+ -- If the id is not provided, it is treated as a new record.
13
14
  --
14
15
  -- # attrs
15
16
  --
@@ -1,8 +1,8 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ohm"
3
- s.version = "2.0.0.alpha5"
3
+ s.version = "2.0.0.rc1"
4
4
  s.summary = %{Object-hash mapping library for Redis.}
5
- s.description = %Q{Ohm is a library that allows to store an object in Redis, a persistent key-value database. It includes an extensible list of validations and has very good performance.}
5
+ s.description = %Q{Ohm is a library that allows to store an object in Redis, a persistent key-value database. It has very good performance.}
6
6
  s.authors = ["Michel Martens", "Damian Janowski", "Cyril David"]
7
7
  s.email = ["michel@soveran.com", "djanowski@dimaion.com", "me@cyrildavid.com"]
8
8
  s.homepage = "http://soveran.github.io/ohm/"
@@ -12,14 +12,14 @@ scope do
12
12
  [john, jane]
13
13
  end
14
14
 
15
- test "Set#count doesn't do each" do
15
+ test "Set#size doesn't do each" do
16
16
  set = Contact.all
17
17
 
18
18
  def set.each
19
19
  raise "Failed"
20
20
  end
21
21
 
22
- assert_equal 2, set.count
22
+ assert_equal 2, set.size
23
23
  end
24
24
 
25
25
  test "Set#each as an Enumerator" do |john, jane|
@@ -67,13 +67,13 @@ scope do
67
67
  end
68
68
  end
69
69
 
70
- test "List#count doesn't do each" do |post, c1, c2|
70
+ test "List#size doesn't do each" do |post, c1, c2|
71
71
  list = post.comments
72
72
 
73
73
  def list.each
74
74
  raise "Failed"
75
75
  end
76
76
 
77
- assert_equal 2, list.count
77
+ assert_equal 2, list.size
78
78
  end
79
79
  end
@@ -1,10 +1,10 @@
1
- __END__
2
1
  require File.expand_path("./helper", File.dirname(__FILE__))
3
2
 
4
3
  class User < Ohm::Model
5
4
  attribute :fname
6
5
  attribute :lname
7
6
  attribute :status
7
+
8
8
  index :fname
9
9
  index :lname
10
10
  index :status
@@ -64,9 +64,24 @@ test "#except" do |john, jane|
64
64
  res = User.all.except(:status => "inactive")
65
65
 
66
66
  assert_equal 2, res.size
67
+ assert res.include?(john)
67
68
  assert res.include?(jane)
68
69
  end
69
70
 
71
+ test "#except unions keys when passing an array" do |john, jane|
72
+ expected = User.create(:fname => "Jean", :status => "inactive")
73
+
74
+ res = User.find(:status => "inactive").except(:fname => [john.fname, jane.fname])
75
+
76
+ assert_equal 1, res.size
77
+ assert res.include?(expected)
78
+
79
+ res = User.all.except(:fname => [john.fname, jane.fname])
80
+
81
+ assert_equal 1, res.size
82
+ assert res.include?(expected)
83
+ end
84
+
70
85
  test "indices bug related to a nil attribute" do |john, jane|
71
86
  # First we create a record with a nil attribute
72
87
  out = User.create(:status => nil, :lname => "Doe")
@@ -77,7 +92,7 @@ test "indices bug related to a nil attribute" do |john, jane|
77
92
 
78
93
  # At this point, the index for the nil attribute should
79
94
  # have been cleared.
80
- assert_equal 0, User.db.scard("User:indices:status:")
95
+ assert_equal 0, User.redis.call("SCARD", "User:indices:status:")
81
96
  end
82
97
 
83
98
  test "#union" do |john, jane|
@@ -150,7 +165,7 @@ scope do
150
165
 
151
166
  assert_equal 1, res.size
152
167
  assert res.map(&:mood).include?("sad")
153
- assert res.map(&:book_id).include?(book2.id)
168
+ assert res.map(&:book_id).include?(book2.id.to_s)
154
169
  end
155
170
 
156
171
  test "@myobie usecase" do |book1, book2|
@@ -160,142 +175,3 @@ scope do
160
175
  assert_equal 2, res.size
161
176
  end
162
177
  end
163
-
164
- # test precision of filtering commands
165
- require "logger"
166
- require "stringio"
167
- scope do
168
- class Post < Ohm::Model
169
- attribute :author
170
- index :author
171
-
172
- attribute :mood
173
- index :mood
174
- end
175
-
176
- setup do
177
- io = StringIO.new
178
-
179
- Post.connect(:logger => Logger.new(io))
180
-
181
- Post.create(author: "matz", mood: "happy")
182
- Post.create(author: "rich", mood: "mad")
183
-
184
- io
185
- end
186
-
187
- def read(io)
188
- io.rewind
189
- io.read
190
- end
191
-
192
- test "SINTERSTORE a b" do |io|
193
- Post.find(author: "matz").find(mood: "happy").to_a
194
-
195
- # This is the simple case. We should only do one SINTERSTORE
196
- # given two direct keys. Anything more and we're performing badly.
197
- expected = "SINTERSTORE Post:tmp:[a-f0-9]{64} " +
198
- "Post:indices:author:matz Post:indices:mood:happy"
199
-
200
- assert(read(io) =~ Regexp.new(expected))
201
- end
202
-
203
- test "SUNIONSTORE a b" do |io|
204
- Post.find(author: "matz").union(mood: "happy").to_a
205
-
206
- # Another simple case where we must only do one operation at maximum.
207
- expected = "SUNIONSTORE Post:tmp:[a-f0-9]{64} " +
208
- "Post:indices:author:matz Post:indices:mood:happy"
209
-
210
- assert(read(io) =~ Regexp.new(expected))
211
- end
212
-
213
- test "SUNIONSTORE c (SINTERSTORE a b)" do |io|
214
- Post.find(author: "matz").find(mood: "happy").union(author: "rich").to_a
215
-
216
- # For this case we need an intermediate key. This will
217
- # contain the intersection of matz + happy.
218
- expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
219
- "Post:indices:author:matz Post:indices:mood:happy"
220
-
221
- assert(read(io) =~ Regexp.new(expected))
222
-
223
- # The next operation is simply doing a UNION of the previously
224
- # generated intermediate key and the additional single key.
225
- expected = "SUNIONSTORE (Post:tmp:[a-f0-9]{64}) " +
226
- "%s Post:indices:author:rich" % $1
227
-
228
- assert(read(io) =~ Regexp.new(expected))
229
- end
230
-
231
- test "SUNIONSTORE (SINTERSTORE c d) (SINTERSTORE a b)" do |io|
232
- Post.find(author: "matz").find(mood: "happy").
233
- union(author: "rich", mood: "sad").to_a
234
-
235
- # Similar to the previous case, we need to do an intermediate
236
- # operation.
237
- expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
238
- "Post:indices:author:matz Post:indices:mood:happy"
239
-
240
- match1 = read(io).match(Regexp.new(expected))
241
- assert match1
242
-
243
- # But now, we need to also hold another intermediate key for the
244
- # condition of author: rich AND mood: sad.
245
- expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
246
- "Post:indices:author:rich Post:indices:mood:sad"
247
-
248
- match2 = read(io).match(Regexp.new(expected))
249
- assert match2
250
-
251
- # Now we expect that it does a UNION of those two previous
252
- # intermediate keys.
253
- expected = sprintf(
254
- "SUNIONSTORE (Post:tmp:[a-f0-9]{64}) %s %s",
255
- match1[1], match2[1]
256
- )
257
-
258
- assert(read(io) =~ Regexp.new(expected))
259
- end
260
-
261
- test do |io|
262
- Post.create(author: "kent", mood: "sad")
263
-
264
- Post.find(author: "kent", mood: "sad").
265
- union(author: "matz", mood: "happy").
266
- except(mood: "sad", author: "rich").to_a
267
-
268
- expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
269
- "Post:indices:author:kent Post:indices:mood:sad"
270
-
271
- match1 = read(io).match(Regexp.new(expected))
272
- assert match1
273
-
274
- expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
275
- "Post:indices:author:matz Post:indices:mood:happy"
276
-
277
- match2 = read(io).match(Regexp.new(expected))
278
- assert match2
279
-
280
- expected = sprintf(
281
- "SUNIONSTORE (Post:tmp:[a-f0-9]{64}) %s %s",
282
- match1[1], match2[1]
283
- )
284
-
285
- match3 = read(io).match(Regexp.new(expected))
286
- assert match3
287
-
288
- expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
289
- "Post:indices:mood:sad Post:indices:author:rich"
290
-
291
- match4 = read(io).match(Regexp.new(expected))
292
- assert match4
293
-
294
- expected = sprintf(
295
- "SDIFFSTORE (Post:tmp:[a-f0-9]{64}) %s %s",
296
- match3[1], match4[1]
297
- )
298
-
299
- assert(read(io) =~ Regexp.new(expected))
300
- end
301
- end
@@ -7,7 +7,6 @@ begin
7
7
  rescue LoadError
8
8
  end
9
9
 
10
- require "rubygems"
11
10
  require "cutest"
12
11
 
13
12
  def silence_warnings
@@ -1,70 +1,63 @@
1
- # encoding: UTF-8
1
+ require_relative 'helper'
2
2
 
3
- require File.expand_path("./helper", File.dirname(__FILE__))
4
-
5
- require "json"
6
3
  require "ohm/json"
7
4
 
8
5
  class Venue < Ohm::Model
9
6
  attribute :name
10
7
  list :programmers, :Programmer
11
-
12
- def validate
13
- assert_present :name
14
- end
15
8
  end
16
9
 
17
10
  class Programmer < Ohm::Model
18
11
  attribute :language
19
12
 
20
- def validate
21
- assert_present :language
22
- end
13
+ index :language
23
14
 
24
15
  def to_hash
25
- super.merge(:language => language)
16
+ super.merge(language: language)
26
17
  end
27
18
  end
28
19
 
29
- test "export an empty hash via to_hash" do
30
- person = Venue.new
31
- assert Hash.new == person.to_hash
32
- end
20
+ test "exports model.to_hash to json" do
21
+ assert_equal Hash.new, JSON.parse(Venue.new.to_json)
33
22
 
34
- test "export a hash with the its id" do
35
- person = Venue.create(:name => "John Doe")
36
- assert_equal Hash[:id => 1], person.to_hash
37
- end
23
+ venue = Venue.create(name: "foo")
24
+ json = JSON.parse(venue.to_json)
25
+ assert_equal venue.id, json["id"]
26
+ assert_equal nil, json["name"]
38
27
 
39
- test "return the merged attributes" do
40
- programmer = Programmer.create(:language => "Ruby")
41
- expected_hash = { :id => 1, :language => 'Ruby' }
28
+ programmer = Programmer.create(language: "Ruby")
29
+ json = JSON.parse(programmer.to_json)
42
30
 
43
- assert expected_hash == programmer.to_hash
31
+ assert_equal programmer.id, json["id"]
32
+ assert_equal programmer.language, json["language"]
44
33
  end
45
34
 
46
- test "just be the to_hash of a model" do
47
- json = JSON.parse(Programmer.create(:language => "Ruby").to_json)
35
+ test "exports a set to json" do
36
+ Programmer.create(language: "Ruby")
37
+ Programmer.create(language: "Python")
48
38
 
49
- assert ["id", "language"] == json.keys.sort
50
- assert 1 == json["id"]
51
- assert "Ruby" == json["language"]
39
+ expected = [{ id: "1", language: "Ruby" }, { id: "2", language: "Python"}].to_json
40
+
41
+ assert_equal expected, Programmer.all.to_json
52
42
  end
53
43
 
54
- test "export an array of records to json" do
55
- Programmer.create(:language => "Ruby")
56
- Programmer.create(:language => "Python")
44
+ test "exports a multiset to json" do
45
+ Programmer.create(language: "Ruby")
46
+ Programmer.create(language: "Python")
57
47
 
58
- expected = [{ :id => "1", :language => "Ruby" }, { :id => "2", :language => "Python"}].to_json
59
- assert_equal expected, Programmer.all.to_json
48
+ expected = [{ id: "1", language: "Ruby" }, { id: "2", language: "Python"}].to_json
49
+ result = Programmer.find(language: "Ruby").union(language: "Python").to_json
50
+
51
+ assert_equal expected, result
60
52
  end
61
53
 
62
- test "export an array of lists to json" do
63
- venue = Venue.create(:name => "Foo")
54
+ test "exports a list to json" do
55
+ venue = Venue.create(name: "Foo")
56
+
57
+ venue.programmers.push(Programmer.create(language: "Ruby"))
58
+ venue.programmers.push(Programmer.create(language: "Python"))
64
59
 
65
- venue.programmers.push(Programmer.create(:language => "Ruby"))
66
- venue.programmers.push(Programmer.create(:language => "Python"))
60
+ expected = [{ id: "1", language: "Ruby" }, { id: "2", language: "Python"}].to_json
67
61
 
68
- expected = [{ :id => "1", :language => "Ruby" }, { :id => "2", :language => "Python"}].to_json
69
62
  assert_equal expected, venue.programmers.to_json
70
63
  end
@@ -7,12 +7,12 @@ require "ostruct"
7
7
  class Post < Ohm::Model
8
8
  attribute :body
9
9
  attribute :published
10
- set :related, Post
10
+ set :related, :Post
11
11
  end
12
12
 
13
13
  class User < Ohm::Model
14
14
  attribute :email
15
- set :posts, Post
15
+ set :posts, :Post
16
16
  end
17
17
 
18
18
  class Person < Ohm::Model
@@ -28,7 +28,7 @@ end
28
28
  class Event < Ohm::Model
29
29
  attribute :name
30
30
  counter :votes
31
- set :attendees, Person
31
+ set :attendees, :Person
32
32
 
33
33
  attribute :slug
34
34
 
@@ -51,10 +51,6 @@ end
51
51
  class Meetup < Ohm::Model
52
52
  attribute :name
53
53
  attribute :location
54
-
55
- def validate
56
- assert_present :name
57
- end
58
54
  end
59
55
 
60
56
  test "booleans" do
@@ -311,8 +307,8 @@ end
311
307
  test "delete an existing model" do
312
308
  class ModelToBeDeleted < Ohm::Model
313
309
  attribute :name
314
- set :foos, Post
315
- set :bars, Post
310
+ set :foos, :Post
311
+ set :bars, :Post
316
312
  end
317
313
 
318
314
  @model = ModelToBeDeleted.create(:name => "Lorem")
@@ -774,7 +770,7 @@ test "be persisted" do
774
770
 
775
771
  assert "foo" == SomeNamespace::Foo[1].name
776
772
  assert "foo" == SomeNamespace::Bar[1].foo.name
777
- end
773
+ end if RUBY_VERSION >= "2.0.0"
778
774
 
779
775
  test "typecast attributes" do
780
776
  class Option < Ohm::Model
@@ -0,0 +1,20 @@
1
+ require_relative 'helper'
2
+
3
+ class Post < Ohm::Model
4
+ end
5
+
6
+ class User < Ohm::Model
7
+ set :posts, :Post
8
+ end
9
+
10
+ test '#exists? returns false if the given id is not included in the set' do
11
+ assert !User.create.posts.exists?('nonexistent')
12
+ end
13
+
14
+ test '#exists? returns true if the given id is included in the set' do
15
+ user = User.create
16
+ post = Post.create
17
+ user.posts.add(post)
18
+
19
+ assert user.posts.exists?(post.id)
20
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'helper'
2
+
3
+ class User < Ohm::Model
4
+ end
5
+
6
+ test "returns an empty hash if model doesn't have set attributes" do
7
+ assert_equal Hash.new, User.new.to_hash
8
+ end
9
+
10
+ test "returns a hash with its id if model is persisted" do
11
+ user = User.create
12
+
13
+ assert_equal Hash[id: user.id], user.to_hash
14
+ end
15
+
16
+ class Person < Ohm::Model
17
+ attribute :name
18
+
19
+ def to_hash
20
+ super.merge(name: name)
21
+ end
22
+ end
23
+
24
+ test "returns additional attributes if the method is overrided" do
25
+ person = Person.create(name: "John")
26
+ expected = { id: person.id, name: person.name }
27
+
28
+ assert_equal expected, person.to_hash
29
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ohm
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.alpha5
4
+ version: 2.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michel Martens
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-10-25 00:00:00.000000000 Z
13
+ date: 2013-12-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: redic
@@ -69,8 +69,7 @@ dependencies:
69
69
  - !ruby/object:Gem::Version
70
70
  version: '0'
71
71
  description: Ohm is a library that allows to store an object in Redis, a persistent
72
- key-value database. It includes an extensible list of validations and has very good
73
- performance.
72
+ key-value database. It has very good performance.
74
73
  email:
75
74
  - michel@soveran.com
76
75
  - djanowski@dimaion.com
@@ -110,7 +109,6 @@ files:
110
109
  - test/counters.rb
111
110
  - test/db/.gitignore
112
111
  - test/enumerable.rb
113
- - test/extensibility.rb
114
112
  - test/filtering.rb
115
113
  - test/hash_key.rb
116
114
  - test/helper.rb
@@ -119,7 +117,9 @@ files:
119
117
  - test/json.rb
120
118
  - test/list.rb
121
119
  - test/model.rb
120
+ - test/set.rb
122
121
  - test/test.conf
122
+ - test/to_hash.rb
123
123
  - test/uniques.rb
124
124
  homepage: http://soveran.github.io/ohm/
125
125
  licenses:
@@ -1,48 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("./helper", File.dirname(__FILE__))
4
-
5
- if defined?(Ohm::Model::PureRuby)
6
- class User < Ohm::Model
7
- attribute :email
8
-
9
- attr_accessor :foo
10
-
11
- def save
12
- super do |t|
13
- t.before do
14
- self.email = email.downcase
15
- end
16
-
17
- t.after do
18
- if @foo
19
- key[:foos].sadd(@foo)
20
- end
21
- end
22
- end
23
- end
24
-
25
- def delete
26
- super do |t|
27
- foos = nil
28
-
29
- t.before do
30
- foos = key[:foos].smembers
31
- end
32
-
33
- t.after do
34
- foos.each { |foo| key[:foos].srem(foo) }
35
- end
36
- end
37
- end
38
- end
39
-
40
- test do
41
- u = User.create(:email => "FOO@BAR.COM", :foo => "bar")
42
- assert_equal "foo@bar.com", u.email
43
- assert_equal ["bar"], u.key[:foos].smembers
44
-
45
- u.delete
46
- assert_equal [], User.key[u.id][:foos].smembers
47
- end
48
- end