ohm 2.0.0.alpha5 → 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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