mongocore 0.1.0 → 0.1.1

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: 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