mongodb 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/Rakefile +3 -5
  2. data/lib/mongodb/driver.rb +33 -0
  3. data/lib/mongodb/driver/collection.rb +156 -0
  4. data/lib/mongodb/driver/database.rb +6 -0
  5. data/lib/mongodb/driver/dynamic_finders.rb +41 -0
  6. data/lib/{mongo_db → mongodb}/driver/spec.rb +12 -12
  7. data/lib/mongodb/gems.rb +6 -0
  8. data/lib/mongodb/integration/locales.rb +4 -0
  9. data/lib/mongodb/integration/locales/activemodel/ru.yml +27 -0
  10. data/lib/mongodb/migration.rb +8 -0
  11. data/lib/mongodb/migration/definition.rb +19 -0
  12. data/lib/mongodb/migration/migration.rb +68 -0
  13. data/lib/mongodb/migration/tasks.rb +19 -0
  14. data/lib/mongodb/model.rb +26 -0
  15. data/lib/mongodb/model/assignment.rb +65 -0
  16. data/lib/mongodb/model/attribute_convertors.rb +54 -0
  17. data/lib/mongodb/model/callbacks.rb +36 -0
  18. data/lib/mongodb/model/crud.rb +57 -0
  19. data/lib/mongodb/model/db.rb +53 -0
  20. data/lib/mongodb/model/misc.rb +33 -0
  21. data/lib/mongodb/model/model.rb +11 -0
  22. data/lib/mongodb/model/query.rb +36 -0
  23. data/lib/mongodb/model/scope.rb +99 -0
  24. data/lib/mongodb/model/spec.rb +12 -0
  25. data/lib/mongodb/model/support/types.rb +110 -0
  26. data/lib/mongodb/model/validation.rb +5 -0
  27. data/lib/mongodb/object.rb +18 -0
  28. data/lib/mongodb/object/object_helper.rb +62 -0
  29. data/lib/mongodb/object/object_serializer.rb +273 -0
  30. data/readme.md +261 -6
  31. data/spec/driver/collection_spec.rb +83 -0
  32. data/spec/{mongo_model/hash → driver}/crud_spec.rb +30 -29
  33. data/spec/driver/database_spec.rb +9 -0
  34. data/spec/driver/dynamic_finders_spec.rb +50 -0
  35. data/spec/driver/fixes_spec.rb +12 -0
  36. data/spec/driver/hash_helper_spec.rb +24 -0
  37. data/spec/driver/spec_helper.rb +28 -0
  38. data/spec/integration/am_conversion_spec.rb +1 -0
  39. data/spec/integration/am_validation_spec.rb +34 -0
  40. data/spec/integration/validatable2_spec.rb +40 -0
  41. data/spec/migration/migration_spec.rb +60 -0
  42. data/spec/model/assignment_spec.rb +80 -0
  43. data/spec/model/attribute_convertors_spec.rb +73 -0
  44. data/spec/model/callbacks_spec.rb +47 -0
  45. data/spec/model/crud_spec.rb +151 -0
  46. data/spec/model/db_spec.rb +63 -0
  47. data/spec/model/misc_spec.rb +58 -0
  48. data/spec/model/query_spec.rb +47 -0
  49. data/spec/model/scope_spec.rb +149 -0
  50. data/spec/model/spec_helper.rb +4 -0
  51. data/spec/model/validation_spec.rb +37 -0
  52. data/spec/object/callbacks_spec.rb +97 -0
  53. data/spec/object/crud_shared.rb +53 -0
  54. data/spec/object/crud_spec.rb +55 -0
  55. data/spec/object/spec_helper.rb +14 -0
  56. data/spec/{mongo_model/object → object}/validation_spec.rb +38 -36
  57. metadata +92 -25
  58. data/lib/mongo_db.rb +0 -3
  59. data/lib/mongo_db/driver.rb +0 -5
  60. data/lib/mongo_db/driver/connection.rb +0 -0
  61. data/lib/mongo_db/driver/database.rb +0 -5
  62. data/lib/mongo_db/gems.rb +0 -2
  63. data/lib/mongo_db/model.rb +0 -0
  64. data/spec/mongo_ext/migration_spec.rb +0 -0
  65. data/spec/mongo_ext/misc_spec.rb +0 -10
  66. data/spec/mongo_ext/spec_helper.rb +0 -4
  67. data/spec/mongo_model/model/crud_spec.rb +0 -123
  68. data/spec/mongo_model/model/query_spec.rb +0 -0
  69. data/spec/mongo_model/object/callbacks_spec.rb +0 -100
  70. data/spec/mongo_model/object/crud_shared.rb +0 -53
  71. data/spec/mongo_model/object/crud_spec.rb +0 -45
  72. data/spec/mongo_model/spec_helper.rb +0 -1
  73. data/spec/query_spec.rb +0 -0
  74. data/spec/test_spec.rb +0 -5
data/readme.md CHANGED
@@ -1,9 +1,264 @@
1
- # Ruby ODM for MongoDB
1
+ Object Model & Ruby driver enhancements for MongoDB.
2
2
 
3
- Not finished, in development.
3
+ 1. Driver enchancements & Migrations.
4
+ 2. Persistence for any Ruby object.
5
+ 3. Object Model (callbacks, validations, mass-assignment, finders, ...).
4
6
 
5
- - Very small.
6
- - Schema-less, dynamic.
7
+ Lower layers are independent from upper, use only what You need.
8
+
9
+ # MongoDB driver enhancements
10
+
11
+ MongoDB itself is very powerful, flexible and simple tool, but the API of the Ruby driver is a little complicated.
12
+ These enhancements alter the driver's API and made it more simple and intuitive.
13
+
14
+ - Makes API of mongo-ruby-driver friendly & handy.
15
+ - No extra abstraction or complexities introduced, all things are exactly the same as in MongoDB.
16
+ - 100% backward compatibility with original driver API (if not - it's a bug, report it please)
17
+
18
+ ``` ruby
19
+ require 'mongodb/driver'
20
+
21
+ # Changing some defaults.
22
+ Mongo.defaults.merge! symbolize: true, multi: true, safe: true
23
+
24
+ # Connection & db.
25
+ connection = Mongo::Connection.new
26
+ db = connection.db 'default_test'
27
+ db.units.drop
28
+
29
+ # Collection shortcuts.
30
+ db.some_collection
31
+
32
+ # Create.
33
+ zeratul = {name: 'Zeratul', stats: {attack: 85, life: 300, shield: 100}}
34
+ tassadar = {name: 'Tassadar', stats: {attack: 0, life: 80, shield: 300}}
35
+
36
+ db.units.save zeratul
37
+ db.units.save tassadar
38
+
39
+ # Udate (we made error - mistakenly set Tassadar's attack as zero, let's fix it).
40
+ tassadar[:stats][:attack] = 20
41
+ db.units.save tassadar
42
+
43
+ # Querying first & all, there's also :each, the same as :all.
44
+ db.units.first name: 'Zeratul' # => zeratul
45
+ db.units.all name: 'Zeratul' # => [zeratul]
46
+ db.units.all name: 'Zeratul' do |unit|
47
+ unit # => zeratul
48
+ end
49
+
50
+ # Dynamic Finders (bang versions also availiable).
51
+ db.units.by_name 'Zeratul' # => zeratul
52
+ db.units.first_by_name 'Zeratul' # => zeratul
53
+ db.units.all_by_name 'Zeratul' # => [zeratul]
54
+
55
+ # Query sugar, use {name: {_gt: 'Z'}} instead of {name: {:$gt => 'Z'}}.
56
+ Mongo.defaults.merge! convert_underscore_to_dollar: true
57
+ db.units.all name: {_gt: 'Z'} # => [zeratul]
58
+ ```
59
+
60
+ Source: examples/driver.rb
61
+
62
+ More docs - there's no need for more docs, the whole point of this extension is to be small, intuitive, 100% compatible with the official driver, and require no extra knowledge.
63
+ So, please use standard Ruby driver documentation.
64
+
65
+ # Persistence for any Ruby object
66
+
67
+ Save any Ruby object to MongoDB, as if it's a document. Objects can be any type, simple or composite with other objects / arrays / hashes inside.
68
+
69
+ Note: the :initialize method should allow to create object without arguments.
70
+
71
+ ``` ruby
72
+ # Let's define the game unit.
73
+ class Unit
74
+ attr_reader :name, :stats
75
+
76
+ # don't forget to allow creating object with no arguments
77
+ def initialize name = nil, stats = nil
78
+ @name, @stats = name, stats
79
+ end
80
+
81
+ class Stats
82
+ attr_accessor :attack, :life, :shield
83
+
84
+ def initialize attack = nil, life = nil, shield = nil
85
+ @attack, @life, @shield = attack, life, shield
86
+ end
87
+ end
88
+ end
89
+
90
+ # Connecting to MongoDB.
91
+ require 'mongodb/object'
92
+ Mongo.defaults.merge! symbolize: true, multi: true, safe: true
93
+ connection = Mongo::Connection.new
94
+ db = connection.db 'default_test'
95
+ db.units.drop
96
+
97
+ # Create.
98
+ zeratul = Unit.new('Zeratul', Unit::Stats.new(85, 300, 100))
99
+ tassadar = Unit.new('Tassadar', Unit::Stats.new(0, 80, 300))
100
+
101
+ db.units.save zeratul
102
+ db.units.save tassadar
103
+
104
+ # Udate (we made error - mistakenly set Tassadar's attack as zero, let's fix it).
105
+ tassadar.stats.attack = 20
106
+ db.units.save tassadar
107
+
108
+ # Querying first & all, there's also :each, the same as :all.
109
+ db.units.first name: 'Zeratul' # => zeratul
110
+ db.units.all name: 'Zeratul' # => [zeratul]
111
+ db.units.all name: 'Zeratul' do |unit|
112
+ unit # => zeratul
113
+ end
114
+
115
+ # Simple finders (bang versions also availiable).
116
+ db.units.by_name 'Zeratul' # => zeratul
117
+ db.units.first_by_name 'Zeratul' # => zeratul
118
+ db.units.all_by_name 'Zeratul' # => [zeratul]
119
+
120
+ # Query sugar, use {name: {_gt: 'Z'}} instead of {name: {:$gt => 'Z'}}.
121
+ Mongo.defaults.merge! convert_underscore_to_dollar: true
122
+ db.units.all name: {_gt: 'Z'} # => [zeratul]
123
+ ```
124
+
125
+ Source: examples/object.rb
126
+
127
+ # Object Model
128
+
129
+ - The same API for pure driver and Models.
130
+ - Minimum extra abstractions, trying to keep things as close to the MongoDB semantic as possible.
131
+ - Schema-less, dynamic (with ability to specify types for mass-assignment).
7
132
  - Models can be saved to any collection.
8
- - Full support for embedded objects (and MDD composite pattern).
9
- - Doesn't try to mimic ActiveRecord, it's differrent and designed to get most of MongoDB.
133
+ - Full support for embedded objects (validations, callbacks, ...).
134
+ - Scope, default_scope
135
+ - Doesn't try to mimic ActiveRecord, MongoDB is differrent and this tool designed to get most of it.
136
+ - Very small, see [code stats][code_stats].
137
+
138
+ Other ODM usually try to cover simple but non-standard API of MongoDB behind complex ORM-like abstractions. This tool **exposes simplicity and power of MongoDB and leverages it's differences**.
139
+
140
+ ``` ruby
141
+ # Connecting to MongoDB.
142
+ require 'mongodb/model'
143
+ Mongo.defaults.merge! symbolize: true, multi: true, safe: true
144
+ connection = Mongo::Connection.new
145
+ db = connection.db 'default_test'
146
+ db.units.drop
147
+ Mongo::Model.db = db
148
+
149
+ # Let's define the game unit.
150
+ class Unit
151
+ inherit Mongo::Model
152
+ collection :units
153
+
154
+ attr_accessor :name, :status, :stats
155
+
156
+ scope :alive, status: 'alive'
157
+
158
+ class Stats
159
+ inherit Mongo::Model
160
+ attr_accessor :attack, :life, :shield
161
+ end
162
+ end
163
+
164
+ # Create.
165
+ zeratul = Unit.build(name: 'Zeratul', status: 'alive', stats: Unit::Stats.build(attack: 85, life: 300, shield: 100))
166
+ tassadar = Unit.build(name: 'Tassadar', status: 'dead', stats: Unit::Stats.build(attack: 0, life: 80, shield: 300))
167
+
168
+ zeratul.save
169
+ tassadar.save
170
+
171
+ # Udate (we made error - mistakenly set Tassadar's attack as zero, let's fix it).
172
+ tassadar.stats.attack = 20
173
+ tassadar.save
174
+
175
+ # Querying first & all, there's also :each, the same as :all.
176
+ Unit.first name: 'Zeratul' # => zeratul
177
+ Unit.all name: 'Zeratul' # => [zeratul]
178
+ Unit.all name: 'Zeratul' do |unit|
179
+ unit # => zeratul
180
+ end
181
+
182
+ # Simple finders (bang versions also availiable).
183
+ Unit.by_name 'Zeratul' # => zeratul
184
+ Unit.first_by_name 'Zeratul' # => zeratul
185
+ Unit.all_by_name 'Zeratul' # => [zeratul]
186
+
187
+ # Scopes.
188
+ Unit.alive.count # => 1
189
+ Unit.alive.first # => zeratul
190
+
191
+ # Callbacks & callbacks on embedded models.
192
+
193
+ # Validations.
194
+
195
+ # Save model to any collection.
196
+ ```
197
+
198
+ Source: examples/model.rb
199
+
200
+ # Migrations
201
+
202
+ Define migration steps, specify desired version and apply it (usually all this should be done via Rake task).
203
+
204
+ ``` ruby
205
+ require 'mongodb/migration'
206
+
207
+ # Connection & db.
208
+ connection = Mongo::Connection.new
209
+ db = connection.db 'default_test'
210
+ db.units.drop
211
+
212
+ # Initialize migration (usually all this should be done inside of :migrate
213
+ # rake task).
214
+ migration = Mongo::Migration.new db
215
+
216
+ # Define migrations.
217
+ # Usually they are defined as files in some folder and You loading it by
218
+ # using something like this:
219
+ # Dir['<runtime_dir>/db/migrations/*.rb'].each{|fname| load fname}
220
+ migration.add 1 do |m|
221
+ m.up{|db| db.units.save name: 'Zeratul'}
222
+ m.down{|db| db.units.remove name: 'Zeratul'}
223
+ end
224
+
225
+ # Let's add another one.
226
+ migration.add 2 do |m|
227
+ m.up{|db| db.units.save name: 'Tassadar'}
228
+ m.down{|db| db.units.remove name: 'Tassadar'}
229
+ end
230
+
231
+ # Specify what version of database You need and apply migration.
232
+ migration.update 2
233
+
234
+ migration.current_version # => 2
235
+ db.units.count # => 2
236
+
237
+ # You can rollback it the same way.
238
+ migration.update 0
239
+
240
+ migration.current_version # => 0
241
+ db.units.count # => 0
242
+
243
+ # To update to the highest version just call it without the version specified
244
+ migration.update
245
+
246
+ migration.current_version # => 2
247
+ db.units.count # => 2
248
+ ```
249
+
250
+ Source: examples/migration.rb
251
+
252
+ # Installation
253
+
254
+ ``` bash
255
+ gem install mongodb
256
+ ```
257
+
258
+ # License
259
+
260
+ Copyright (c) Alexey Petrushin, http://petrush.in, released under the MIT license.
261
+
262
+ [mongo_mapper_ext]: https://github.com/alexeypetrushin/mongo_mapper_ext
263
+ [mongoid_misc]: https://github.com/alexeypetrushin/mongoid_misc
264
+ [code_stats]: https://github.com/alexeypetrushin/mongodb/raw/master/docs/code_stats.png
@@ -0,0 +1,83 @@
1
+ require 'driver/spec_helper'
2
+
3
+ describe "Collection" do
4
+ with_mongo
5
+
6
+ it 'by default save must update all matched by criteria (not first as defautl in mongo)' do
7
+ db.units.save name: 'Probe', race: 'Protoss', status: 'alive'
8
+ db.units.save name: 'Zealot', race: 'Protoss', status: 'alive'
9
+
10
+ # update
11
+ db.units.update({race: 'Protoss'}, :$set => {status: 'dead'})
12
+ db.units.all.collect{|u| u[:status]}.should == %w(dead dead)
13
+
14
+ # destroy
15
+ db.units.destroy race: 'Protoss'
16
+ db.units.count.should == 0
17
+ end
18
+
19
+ describe "symbolize" do
20
+ it 'should always return symbolized hashes' do
21
+ zeratul = {name: 'Zeratul'}
22
+ db.units.save(zeratul).should be_mongo_id
23
+ r = db.units.first(name: 'Zeratul')
24
+ r[:_id].should be_mongo_id
25
+ r['_id'].should be_nil
26
+ r[:name].should == 'Zeratul'
27
+ r['name'].should be_nil
28
+ end
29
+
30
+ it "should be able to disable symbolization" do
31
+ old = Mongo.defaults[:symbolize]
32
+ begin
33
+ Mongo.defaults[:symbolize] = false
34
+
35
+ zeratul = {name: 'Zeratul'}
36
+ db.units.save(zeratul).should be_mongo_id
37
+ r = db.units.first(name: 'Zeratul')
38
+ r[:_id].should be_nil
39
+ r['_id'].should be_mongo_id
40
+ r[:name].should be_nil
41
+ r['name'].should == 'Zeratul'
42
+ ensure
43
+ Mongo.defaults[:symbolize] = old
44
+ end
45
+ end
46
+ end
47
+
48
+ it "first" do
49
+ db.units.first.should be_nil
50
+ zeratul = {name: 'Zeratul'}
51
+ db.units.save(zeratul).should be_mongo_id
52
+ db.units.first(name: 'Zeratul')[:name].should == 'Zeratul'
53
+ end
54
+
55
+ it 'all' do
56
+ db.units.all.should == []
57
+
58
+ zeratul = {name: 'Zeratul'}
59
+ db.units.save(zeratul).should be_mongo_id
60
+
61
+ list = db.units.all(name: 'Zeratul')
62
+ list.size.should == 1
63
+ list.first[:name].should == 'Zeratul'
64
+
65
+ # with block
66
+ list = []; db.units.all{|o| list << o}
67
+ list.size.should == 1
68
+ list.first[:name].should == 'Zeratul'
69
+ end
70
+
71
+ it 'count' do
72
+ db.units.count(name: 'Zeratul').should == 0
73
+ db.units.save name: 'Zeratul'
74
+ db.units.save name: 'Tassadar'
75
+ db.units.count(name: 'Zeratul').should == 1
76
+ end
77
+
78
+ it "underscore to dollar" do
79
+ db.units.save name: 'Jim', age: 34
80
+ db.units.save name: 'Zeratul', age: 600
81
+ db.units.all(age: {_lt: 100}).count.should == 1
82
+ end
83
+ end
@@ -1,53 +1,54 @@
1
- require 'spec_helper'
1
+ require 'driver/spec_helper'
2
2
 
3
3
  describe "Hash CRUD" do
4
- with_mongo_model
5
-
4
+ with_mongo
5
+
6
6
  describe 'simple' do
7
7
  before do
8
8
  @zeratul = {name: 'Zeratul', info: 'Dark Templar'}
9
9
  end
10
-
10
+
11
11
  it 'crud' do
12
12
  # read
13
- db.heroes.count.should == 0
14
- db.heroes.all.should == []
15
- db.heroes.first.should == nil
16
-
13
+ db.units.count.should == 0
14
+ db.units.all.should == []
15
+ db.units.first.should == nil
16
+
17
17
  # create
18
- db.heroes.save(@zeratul)
19
-
18
+ db.units.save(@zeratul).should be_mongo_id
19
+ @zeratul[:_id].should be_mongo_id
20
+
20
21
  # read
21
- db.heroes.all.should == [@zeratul]
22
- db.heroes.count.should == 1
23
- db.heroes.first.should == @zeratul
24
-
22
+ db.units.all.should == [@zeratul]
23
+ db.units.count.should == 1
24
+ db.units.first.should == @zeratul
25
+
25
26
  # update
26
27
  @zeratul[:info] = 'Killer of Cerebrates'
27
- db.heroes.save({name: 'Zeratul'}, @zeratul)
28
- db.heroes.count.should == 1
29
- db.heroes.first(name: 'Zeratul').info.should == 'Killer of Cerebrates'
30
-
28
+ db.units.save @zeratul
29
+ db.units.count.should == 1
30
+ db.units.first(name: 'Zeratul')[:info].should == 'Killer of Cerebrates'
31
+
31
32
  # destroy
32
- db.heroes.destroy name: 'Zeratul'
33
- db.heroes.count.should == 0
33
+ db.units.destroy @zeratul
34
+ db.units.count.should == 0
34
35
  end
35
36
  end
36
-
37
+
37
38
  describe 'embedded' do
38
- before do
39
+ before do
39
40
  @player = {
40
41
  name: 'Alex',
41
42
  missions: [
42
43
  {name: 'Wasteland', stats: {buildings: 5, units: 10}},
43
44
  {name: 'Backwater Station', stats: {buildings: 8, units: 25}}
44
45
  ]
45
- }
46
+ }
46
47
  end
47
-
48
+
48
49
  it 'crud' do
49
50
  # create
50
- db.players.save(@player)
51
+ db.players.save(@player).should be_mongo_id
51
52
 
52
53
  # read
53
54
  db.players.count.should == 1
@@ -55,15 +56,15 @@ describe "Hash CRUD" do
55
56
 
56
57
  # update
57
58
  @player[:missions].first[:stats][:units] = 9
58
- @player.missions << {name: 'Desperate Alliance', stats: {buildings: 11, units: 40}},
59
- db.players.save({name: 'Alex'}, @player)
59
+ @player[:missions].push name: 'Desperate Alliance', stats: {buildings: 11, units: 40}
60
+ db.players.save(@player).should_not be_nil
60
61
  db.players.count.should == 1
61
62
  db.players.first.should == @player
62
63
  db.players.first.object_id.should_not == @player.object_id
63
64
 
64
65
  # destroy
65
- db.players.destroy name: 'Alex'
66
+ db.players.destroy @player
66
67
  db.players.count.should == 0
67
68
  end
68
- end
69
+ end
69
70
  end