mongocore 0.1.0 → 0.1.1

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: 15559a6ab53895176145f773faaff205c501289e
4
- data.tar.gz: 29b028ec25e1035047404b83fcaa3a342162c640
3
+ metadata.gz: 930f0c5ba53f15b7014cb473803554975acf1679
4
+ data.tar.gz: 6d84ab1c806b72b558641f89cbb3608d7ee34c79
5
5
  SHA512:
6
- metadata.gz: 8ae672dfcca254ff415fc76d25485af917cbd1dff5e750e1d688aa2160b92db56ef9e323358b6a291aaae0a4231c002ec6fd0c723c8be07e0deff9f10a86ca6f
7
- data.tar.gz: bc320ce559c4372cd884f192c28aa98f0351d5a789adc1348c5bbf993cc02b0799af35876712362b4567b4a904b393459bed0c0b67eaf2e8609df000626edf24
6
+ metadata.gz: 750dcd341fee0a1266951f93705d6aeb02a03f5b0ef44af2c439ccc5d2db0d284da1d241a325393982662b88a436b61cecf768850547d252e0842dfd1fc6b42c
7
+ data.tar.gz: 9a98403d956ae87fec94e8833a6bdd6acd9d0ed9ea2c4979629b84ce0f69ff72b6542b04da5b87cd6dfb657f239cdccea37c371d9574647959714080d65a902e
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .bundle
2
+ Gemfile.lock
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ **Version 0.1.0** - *2017-01-05*
2
+
3
+ - Automatic saving of created_at and updated_at keys if they exist
4
+ - All before and after filters working
5
+ - Tagged keys working
6
+ - Can do m.reload instead of m = m.reload
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rerun'
7
+ gem 'rb-fsevent'
8
+ gem 'terminal-notifier'
9
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Fugroup
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,367 @@
1
+ # Mongocore Ruby Database Driver
2
+ A new MongoDB ORM implementation on top of the [MongoDB Ruby driver.](https://docs.mongodb.com/ruby-driver/master/quick-start/) Very fast and light weight.
3
+
4
+ The perfect companion for Sinatra or other Rack-based web frameworks.
5
+
6
+ ### Features
7
+ With Mongocore you can do:
8
+
9
+ * Insert, update and delete
10
+ * Finding, sorting, limit, defaults
11
+ * Scopes, associations, validations
12
+ * Read and write access control for each key
13
+ * Request cache, counter cache, track changes
14
+ * Automatic timestamps, tagged keys
15
+
16
+ The schema is specified with a YAML file which supports default values, data types, and security levels for each key.
17
+
18
+ Please read [the source code](https://github.com/fugroup/mongocore/tree/master/lib/mongocore) to see how it works, it's fully commented and very small, only 7 files, and 354 lines of fully test driven code.
19
+
20
+ | Library | Files | Comment | Lines of code |
21
+ | -------------------------------------- | ----- | ------- | ------------- |
22
+ | [Mongoid](http://mongoid.com) | 256 | 14371 | 10590 |
23
+ | [MongoMapper](http://mongomapper.com) | 91 | 200 | 4070 |
24
+ | [Mongocore](http://mongocore.com) | 7 | 224 | 354 |
25
+
26
+ <br>
27
+ If you are looking for something even lighter, we also [have Minimongo,](https://github.com/fugroup/minimongo) the world's tiniest MongoDB library.
28
+
29
+ The tests are written [using Futest,](https://github.com/fugroup/futest) try it out if you haven't, it makes testing so much fun.
30
+
31
+ ### Installation
32
+ ```
33
+ gem install mongocore
34
+ ```
35
+ or add to your Gemfile.
36
+
37
+ Then in your model:
38
+ ```ruby
39
+ class Model
40
+ include Mongocore::Document
41
+ end
42
+ ```
43
+
44
+ ### Settings
45
+ Mongocore has a few built in settings you can easily toggle:
46
+ ```ruby
47
+ # Schema path is $app_root/config/db/schema/:model.yml
48
+ # The yml files should have singular names
49
+ Mongocore.schema = File.join(Dir.pwd, 'config', 'db', 'schema')
50
+
51
+ # The cache stores documents in memory to avoid db round trips
52
+ Mongocore.cache = true
53
+
54
+ # The access enables the read / write access levels for the keys
55
+ Mongocore.access = true
56
+
57
+ # Enable timestamps, auto-save created_at and updated_at keys
58
+ Mongocore.timestamps = true
59
+
60
+ # Enable debug to see caching information and help
61
+ Mongocore.debug = false
62
+ ```
63
+
64
+ ### Usage
65
+
66
+ ```ruby
67
+ # Create a new document
68
+ m = Model.new
69
+ m.duration = 59
70
+ m.save
71
+
72
+ # Create another document
73
+ p = Parent.new(:house => 'Nice')
74
+ p.save
75
+
76
+ # Reload the model attributes from the database
77
+ p.reload
78
+
79
+ # Add the parent to the model
80
+ m.parent = p
81
+ m.save
82
+
83
+ # Finding
84
+ query = Model.find
85
+
86
+ # Query doesn't get executed until you call all, count, last or first
87
+ m = query.all
88
+ a = query.featured.all
89
+ c = query.count
90
+ l = query.last
91
+ f = query.first
92
+
93
+ # All
94
+ m = Model.find.all
95
+
96
+ # All of these can be used:
97
+ # https://docs.mongodb.com/manual/reference/operator/query-comparison
98
+ m = Model.find(:house => {:$ne => nil, :$eq => 'Nice'}).last
99
+
100
+ # Sorting, use -1 for descending, 1 for ascending
101
+ m = Model.find({:duration => {:$gt => 40}}, {}, :sort => {:duration => -1}).all
102
+ m = p.models.find(:duration => 10).sort(:duration => -1).first
103
+
104
+ # Limit, pass as third option to find or chain, up to you
105
+ p = Parent.find.sort(:duration => 1).limit(5).all
106
+ p = Parent.limit(1).last
107
+ m = p.models.find({}, {}, :sort => {:goal => 1}, :limit => 1).first
108
+ m = Model.sort(:goal => 1, :duration => -1).limit(10).all
109
+
110
+ # First
111
+ m = Model.find(:_id => object_id).first
112
+ m = Model.find(object_id).first
113
+ m = Model.find(string).first
114
+ m = Model.find(:duration => 60, :goal => {:$gt => 0}).first
115
+
116
+ # Last
117
+ m = Model.last
118
+ m = p.models.last
119
+
120
+ # Count
121
+ c = Model.count
122
+ c = p.models.featured.count
123
+
124
+ # Tagged keys for attributes and to_json
125
+ m = Model.first
126
+ m.attributes # => All attributes
127
+ m.attributes(:badge) # => Attributes with the badge tag only
128
+ m.to_json # => All attributes as json
129
+ m.to_json(:badge, :test) # => Pass multiple tags if needed
130
+
131
+ # Track changes
132
+ m.duration = 33
133
+ m.changed?
134
+ m.duration_changed?
135
+ m.duration_was
136
+ m.changes
137
+ m.saved?
138
+ m.unsaved?
139
+
140
+ # Validate
141
+ m.valid?
142
+ m.errors.any?
143
+ m.errors
144
+
145
+ # Update
146
+ m.update(:duration => 60)
147
+
148
+ # Delete
149
+ m.delete
150
+
151
+ # Many associations
152
+ q = p.models.all
153
+ m = p.models.first
154
+ m = p.models.last
155
+
156
+ # Scopes
157
+ q = p.models.featured.all
158
+ q = p.models.featured.nested.all
159
+ m = Model.featured.first
160
+
161
+ # In your model
162
+ class Model
163
+ include Mongocore::Document
164
+
165
+ # Validations will be run if you pass model.save(:validate => true)
166
+ # You can run them manually by calling model.valid?
167
+ # You can have multiple validate blocks if you want to
168
+ validate do
169
+ # The errors hash can be used to collect error messages.
170
+ errors[:duration] << 'duration must be greater than 0' if duration and duration < 1
171
+ errors[:goal] << 'you need a higher goal' if goal and goal < 5
172
+ end
173
+
174
+ # Before and after, filters: :save, :update, :delete
175
+ # You can have multiple blocks for each filter if needed
176
+ before :save, :setup
177
+
178
+ def setup
179
+ puts "Before save"
180
+ end
181
+
182
+ after :delete do
183
+ puts "After delete"
184
+ end
185
+ end
186
+
187
+ # Use pure Ruby driver, returns BSON::Document objects
188
+ Mongocore.db[:models].find.to_a
189
+ Mongocore.db[:models].find({:_id => m._id}).first
190
+
191
+ # Indexing
192
+ Mongocore.db[:models].indexes.create_one({:key => 1})
193
+ Mongocore.db[:models].indexes.create_one({:key => 1}, :unique => true)
194
+ ```
195
+
196
+ ### Schema
197
+ For keys, defaults, description, counters, associations, scopes and accessors, use a schema file written in [YAML.](http://yaml.org)
198
+
199
+ #### Parent example schema
200
+ ```yml
201
+
202
+ # The meta is information about your model
203
+ meta:
204
+ name: parent
205
+ type: document
206
+
207
+ keys:
208
+
209
+ # Use the _id everywhere. The id can be used for whatever you want.
210
+ # @desc: Describes the key, can be used for documentation.
211
+ # @type: object_id, string, integer, float, boolean, time, hash, array
212
+ # @default: the default value for the key when you call .new
213
+ # @read: access level for read: all, user, dev, admin, super, app
214
+ # @write: access level for write. Returns nil if no access, as on read
215
+
216
+ # Object ID, usually added for each model
217
+ _id:
218
+ desc: Unique id
219
+ type: object_id
220
+ read: all
221
+ write: app
222
+
223
+ # String key
224
+ world:
225
+ desc: Parent world
226
+ type: string
227
+ read: all
228
+ write: user
229
+
230
+ # If the key ends with _count, it will be used automatically when
231
+ # you call .count on the model as an automatic caching mechanism
232
+ models_count:
233
+ desc: Models count
234
+ type: integer
235
+ default: 0
236
+ read: all
237
+ write: app
238
+
239
+ # This field will be returned when you write models.featured.count
240
+ # Remember to create an after filter to keep it updated
241
+ models_featured_count:
242
+ desc: Models featured count
243
+ type: integer
244
+ default: 0
245
+ read: all
246
+ write: app
247
+
248
+ # Many relationships lets you do:
249
+ # Model.parents.all or model.parents.featured.all with scopes
250
+ many:
251
+ models:
252
+ dependent: destroy
253
+ ```
254
+
255
+
256
+ #### Model example schema
257
+
258
+ ```yml
259
+ meta:
260
+ name: model
261
+ type: document
262
+
263
+ keys:
264
+ # Object ID
265
+ _id:
266
+ desc: Unique id
267
+ type: object_id
268
+ read: all
269
+ write: app
270
+
271
+ # Integer key with default
272
+ duration:
273
+ desc: Model duration in days
274
+ type: integer
275
+ default: 60
276
+ read: dev
277
+ write: user
278
+ # Add tags for keys for use with attributes and to_json
279
+ tags:
280
+ - badge
281
+
282
+ # Time key
283
+ expires_at:
284
+ desc: Model expiry date
285
+ type: time
286
+ read: all
287
+ write: dev
288
+ # Multiple tags possible: to_json(:badge, :campaigns)
289
+ tags:
290
+ - badge
291
+ - campaigns
292
+
293
+ # Hash key
294
+ location_data:
295
+ desc: Model location data
296
+ type: hash
297
+ read: all
298
+ write: user
299
+
300
+ # Counter key
301
+ votes_count:
302
+ desc: Votes count
303
+ type: integer
304
+ default: 0
305
+ read: all
306
+ write: dev
307
+ tags:
308
+ - badge
309
+
310
+ # If the key ends with _id, it is treated as a foreign key,
311
+ # and you can access it from the referencing model and set it too.
312
+ # Example: model.parent, model.parent = parent
313
+ parent_id:
314
+ desc: Parent id
315
+ type: object_id
316
+ read: all
317
+ write: dev
318
+
319
+ # Generate accessors (attr_accessor) for each key
320
+ accessor:
321
+ - submittable
322
+ - update_expires_at
323
+ - skip_before_save
324
+
325
+ # Define scopes that lets you do Models.featured.count
326
+ # Each scope has a name, and a set of triggers
327
+ scopes:
328
+
329
+ # This will create a .featured scope, and add :duration => 60 to the query.
330
+ featured:
331
+ duration: 60
332
+
333
+ nested:
334
+ goal: 10
335
+
336
+ # Any mongodb driver query is possible
337
+ finished:
338
+ duration: 60
339
+ goal:
340
+ $gt: 10
341
+
342
+ active:
343
+ params:
344
+ - duration
345
+ duration:
346
+ $ne: duration
347
+
348
+ # You can also pass parameters into the scope, as a lambda.
349
+ ending:
350
+ params:
351
+ - user
352
+ $or:
353
+ - user_id: user.id
354
+ - listener: user.id
355
+ - listener: user.link
356
+ deletors:
357
+ $ne: user.id
358
+ ```
359
+
360
+ ### Contribute
361
+ Contributions and feedback are welcome! MIT Licensed.
362
+
363
+ Issues will be fixed, this library is actively maintained by [Fugroup Ltd.](http://www.fugroup.net) We are the creators of [CrowdfundHQ.](https://crowdfundhq.com)
364
+
365
+ Thanks!
366
+
367
+ `@authors: Vidar`
data/config/boot.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'bundler/setup'
2
+ Bundler.require(:default, :development, :test)
3
+
4
+ MODE = ENV['RACK_ENV'] || 'test'
5
+
6
+ require './lib/mongocore.rb'
7
+
8
+ # Add settings here
9
+ Mongocore.schema = File.join(Dir.pwd, 'config', 'db', 'schema')
10
+
11
+ require './models/parent.rb'
12
+ require './models/model.rb'
13
+
14
+ # Logging verbosity
15
+ Mongo::Logger.logger.level = ::Logger::DEBUG
16
+ Mongo::Logger.logger.level = ::Logger::FATAL
17
+
18
+ # To make the driver log to a logfile instead:
19
+ # Mongo::Logger.logger = ::Logger.new('mongo.log')
20
+ # Mongo::Logger.logger.level = ::Logger::INFO
21
+
22
+ # Connect to DB
23
+ Mongocore.db = Mongo::Client.new(['127.0.0.1:27017'], :database => "mongocore_#{MODE}")
@@ -0,0 +1,103 @@
1
+ ---
2
+ meta:
3
+ name: model
4
+ type: document
5
+ keys:
6
+ _id:
7
+ desc: Unique id
8
+ type: object_id
9
+ read: all
10
+ write: app
11
+ link:
12
+ desc: Model link
13
+ read: all
14
+ write: dev
15
+ tags:
16
+ - badge
17
+ goal:
18
+ desc: Model goal
19
+ type: float
20
+ read: all
21
+ write: user
22
+ tags:
23
+ - badge
24
+ duration:
25
+ desc: Model duration in days
26
+ type: integer
27
+ default: 60
28
+ read: dev
29
+ write: user
30
+ tags:
31
+ - badge
32
+ expires_at:
33
+ desc: Model expiry date
34
+ type: time
35
+ read: all
36
+ write: dev
37
+ tags:
38
+ - badge
39
+ location_data:
40
+ desc: Model location data
41
+ type: hash
42
+ read: all
43
+ write: user
44
+ reminders_sent:
45
+ desc: Reminders sent?
46
+ type: boolean
47
+ default: false
48
+ read: user
49
+ write: dev
50
+ votes_count:
51
+ desc: Votes count
52
+ type: integer
53
+ default: 0
54
+ read: all
55
+ write: dev
56
+ tags:
57
+ - badge
58
+ parent_id:
59
+ desc: Parent id
60
+ type: object_id
61
+ read: all
62
+ write: dev
63
+ created_at:
64
+ desc: Created at
65
+ type: time
66
+ read: all
67
+ write: dev
68
+ updated_at:
69
+ desc: Updated at
70
+ type: time
71
+ read: all
72
+ write: dev
73
+
74
+ accessor:
75
+ - submittable
76
+ - update_expires_at
77
+ - skip_before_save
78
+ - owner
79
+ - validate_campaign_fields
80
+ - update_link
81
+ scopes:
82
+ featured:
83
+ duration: 60
84
+ nested:
85
+ goal: 10
86
+ finished:
87
+ duration: 60
88
+ goal:
89
+ $gt: 10
90
+ active:
91
+ params:
92
+ - duration
93
+ duration:
94
+ $ne: duration
95
+ ending:
96
+ params:
97
+ - user
98
+ $or:
99
+ - user_id: user.id
100
+ - listener: user.id
101
+ - listener: user.link
102
+ deletors:
103
+ $ne: user.id
@@ -0,0 +1,41 @@
1
+ ---
2
+ meta:
3
+ name: parent
4
+ type: document
5
+ keys:
6
+ _id:
7
+ desc: Unique id
8
+ type: object_id
9
+ read: all
10
+ write: app
11
+ house:
12
+ desc: Parent house
13
+ type: string
14
+ read: all
15
+ write: user
16
+ models_count:
17
+ desc: Models count
18
+ type: integer
19
+ default: 0
20
+ read: all
21
+ write: app
22
+ models_featured_count:
23
+ desc: Models featured count
24
+ type: integer
25
+ default: 0
26
+ read: all
27
+ write: app
28
+ models_featured_nested_count:
29
+ desc: Models nested featured count
30
+ type: integer
31
+ default: 0
32
+ read: all
33
+ write: app
34
+ link:
35
+ desc: Link
36
+ read: all
37
+ write: dev
38
+
39
+ many:
40
+ models:
41
+ dependent: destroy
@@ -0,0 +1,63 @@
1
+ module Mongocore
2
+ class Access
3
+
4
+ # # # # # # # #
5
+ # The Access class is responsible for checking if an attribute
6
+ # can be read or written. It uses 6 access levels and the
7
+ # read and write attributes in the schema yml file for the model.
8
+ #
9
+ # If your current access level is above the key level, then you
10
+ # can read or write, if not you get nil. This is very useful for APIs
11
+ # where f.ex. you want to show the email to logged in users, but not to all.
12
+ #
13
+
14
+ # Access levels (6)
15
+ AL = [:all, :user, :dev, :admin, :super, :app]
16
+
17
+ # Holds the keys from the model schema
18
+ attr_accessor :keys
19
+
20
+ # The access control class
21
+ def initialize(schema)
22
+ @keys = schema.keys
23
+ end
24
+
25
+ # Set the current access level
26
+ def set(level = nil)
27
+ set?(level) ? RequestStore.store[:access] = level : get
28
+ end
29
+
30
+ # Get the current access level
31
+ def get
32
+ RequestStore.store[:access]
33
+ end
34
+
35
+ # Reset the access level
36
+ def reset
37
+ RequestStore.store[:access] = nil
38
+ end
39
+
40
+ # Key readable?
41
+ def read?(key)
42
+ ok?(keys[key][:read]) rescue false
43
+ end
44
+
45
+ # Key writable?
46
+ def write?(key)
47
+ ok?(keys[key][:write]) rescue false
48
+ end
49
+
50
+ private
51
+
52
+ # Set?
53
+ def set?(level)
54
+ AL.index(level) > AL.index(get || :all)
55
+ end
56
+
57
+ # Ok?
58
+ def ok?(level)
59
+ !Mongocore.access || AL.index(level.to_sym) <= AL.index(get || :app)
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,41 @@
1
+ module Mongocore
2
+
3
+ # # # # # # # #
4
+ # The Cache class keeps track of cache entries.
5
+ #
6
+ # Every query is cached, used the state as the cache key. This is a
7
+ # very aggressive strategy, where arrays won't get updated on update or delete.
8
+ #
9
+
10
+ class Cache
11
+
12
+ # Accessors
13
+ attr_accessor :query, :cache, :key, :type
14
+
15
+ # Init
16
+ def initialize(q)
17
+ @query = q
18
+ @cache = (RequestStore[:cache] ||= {})
19
+ @key = Digest::MD5.hexdigest(@query.key)
20
+ end
21
+
22
+ # Get the cache key
23
+ def get(t)
24
+ @cache[t = key + t.to_s].tap{|d| stat(d, t) if Mongocore.debug}
25
+ end
26
+
27
+ # Set the cache key
28
+ def set(t, v = nil)
29
+ t = key + t.to_s; v ? cache[t] = v : cache.delete(t)
30
+ end
31
+
32
+ private
33
+
34
+ # Stats for debug and cache
35
+ def stat(d, t)
36
+ puts('Cache ' + (d ? 'Hit!' : 'Miss') + ': ' + t)
37
+ RequestStore[d ? :h : :m] = (RequestStore[d ? :h : :m] || 0) + 1
38
+ end
39
+
40
+ end
41
+ end