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 +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.md +367 -0
- data/config/boot.rb +23 -0
- data/config/db/schema/model.yml +103 -0
- data/config/db/schema/parent.yml +41 -0
- data/lib/mongocore/access.rb +63 -0
- data/lib/mongocore/cache.rb +41 -0
- data/lib/mongocore/document.rb +301 -0
- data/lib/mongocore/filters.rb +45 -0
- data/lib/mongocore/query.rb +125 -0
- data/lib/mongocore/schema.rb +117 -0
- data/lib/mongocore.rb +5 -0
- data/models/model.rb +44 -0
- data/models/parent.rb +30 -0
- data/mongocore.gemspec +22 -0
- metadata +18 -15
@@ -0,0 +1,301 @@
|
|
1
|
+
module Mongocore
|
2
|
+
module Document
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# # # # # # # # #
|
6
|
+
# The Document module holds data and methods for your models:
|
7
|
+
#
|
8
|
+
# class Model
|
9
|
+
# include Mongocore::Document
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# Then after that create a model with m = Model.new
|
13
|
+
#
|
14
|
+
# The Model class, accessible from Model or m.class, holds the data
|
15
|
+
# for your models like the schema and the keys.
|
16
|
+
#
|
17
|
+
# The model instance, m, lets you do operations on a single model
|
18
|
+
# like m.save, m.update, m.delete
|
19
|
+
#
|
20
|
+
|
21
|
+
included do
|
22
|
+
|
23
|
+
# Accessors, everything is writable if you need something dynamic.
|
24
|
+
class << self
|
25
|
+
attr_accessor :schema, :access, :filters
|
26
|
+
end
|
27
|
+
|
28
|
+
# Schema
|
29
|
+
@schema = Mongocore::Schema.new(self)
|
30
|
+
|
31
|
+
# Access
|
32
|
+
@access = Mongocore::Access.new(@schema)
|
33
|
+
|
34
|
+
# Filters
|
35
|
+
@filters = Mongocore::Filters.new(self)
|
36
|
+
|
37
|
+
# # # # # # # # # # #
|
38
|
+
# Instance variables
|
39
|
+
# @errors is used for validates
|
40
|
+
# @changes keeps track of object changes
|
41
|
+
# @saved indicates whether this is saved or not
|
42
|
+
#
|
43
|
+
|
44
|
+
attr_accessor :errors, :changes, :saved
|
45
|
+
|
46
|
+
|
47
|
+
# # # # # # # # # # #
|
48
|
+
# The class initializer, called when you write Model.new
|
49
|
+
# Pass in attributes you want to set: Model.new(:duration => 60)
|
50
|
+
# Defaults are filled in automatically.
|
51
|
+
#
|
52
|
+
|
53
|
+
def initialize(a = {})
|
54
|
+
a = a.deep_symbolize_keys
|
55
|
+
|
56
|
+
# The _id has the BSON object, create new unless it exists
|
57
|
+
a[:_id] ? @saved = true : a[:_id] = BSON::ObjectId.new
|
58
|
+
|
59
|
+
# The errors hash
|
60
|
+
@errors = Hash.new{|h, k| h[k] = []}
|
61
|
+
|
62
|
+
# Defaults
|
63
|
+
self.class.schema.defaults.each{|k, v| write(k, v)}
|
64
|
+
|
65
|
+
# Set the attributes
|
66
|
+
a.each{|k, v| write(k, v)}
|
67
|
+
|
68
|
+
# The changes hash
|
69
|
+
@changes = Hash.new{|h, k| h[k] = []}
|
70
|
+
end
|
71
|
+
|
72
|
+
# # # # # # # # # # # #
|
73
|
+
# Model methods are called with m = Model.new, m.method_name
|
74
|
+
# # # # # # # # # # # # # # # # # #
|
75
|
+
#
|
76
|
+
# Database methods
|
77
|
+
#
|
78
|
+
|
79
|
+
# Save attributes to db
|
80
|
+
def save(o = {})
|
81
|
+
# Send :validate => true to validate
|
82
|
+
return nil unless valid? if o[:validate]
|
83
|
+
|
84
|
+
# Create a new query
|
85
|
+
filter(:save){mq(self.class, {:_id => @_id}).update(attributes)}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Update document in db
|
89
|
+
def update(a = {})
|
90
|
+
a.each{|k, v| write(k, v)}; filter(:update){single.update(a)}
|
91
|
+
end
|
92
|
+
|
93
|
+
# Delete a document in db
|
94
|
+
def delete
|
95
|
+
filter(:delete, false){single.delete}
|
96
|
+
end
|
97
|
+
|
98
|
+
# Run filters before and after accessing the db
|
99
|
+
def filter(cmd, saved = true, &block)
|
100
|
+
run(:before, cmd); yield.tap{@saved = saved; run(:after, cmd)}
|
101
|
+
end
|
102
|
+
|
103
|
+
# Reload the document from db and update attributes
|
104
|
+
def reload
|
105
|
+
single.first.tap{|m| attributes = m.attributes}
|
106
|
+
end
|
107
|
+
|
108
|
+
# Set the timestamps if enabled
|
109
|
+
def timestamps
|
110
|
+
t = Time.now.utc; @updated_at = t; @created_at = t if unsaved?
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# # # # # # # # # # # # # # # #
|
115
|
+
# Attribute methods
|
116
|
+
#
|
117
|
+
|
118
|
+
# Collect the attributes, pass tags like defined in your model yml
|
119
|
+
def attributes(*tags)
|
120
|
+
a = {}; self.class.schema.attributes(tags.map(&:to_s)).each{|k| a[k] = read!(k)}; a
|
121
|
+
end
|
122
|
+
|
123
|
+
# Set the attributes
|
124
|
+
def attributes=(a)
|
125
|
+
a.each{|k, v| write!(k, v)}
|
126
|
+
end
|
127
|
+
|
128
|
+
# Changed?
|
129
|
+
def changed?
|
130
|
+
changes.any?
|
131
|
+
end
|
132
|
+
|
133
|
+
# JSON format, pass tags as symbols: to_json(:badge, :gun)
|
134
|
+
def to_json(*args)
|
135
|
+
attributes(*args).to_json
|
136
|
+
end
|
137
|
+
|
138
|
+
# # # # # # # # # # # # # # # #
|
139
|
+
# Validation methods
|
140
|
+
#
|
141
|
+
|
142
|
+
# Valid?
|
143
|
+
def valid?
|
144
|
+
self.class.filters.valid?(self)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Available filters are :save, :update, :delete
|
148
|
+
def run(filter, key = nil)
|
149
|
+
self.class.filters.run(self, filter, key)
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
# # # # # # # # # # # # # # # #
|
154
|
+
# Convenience methods
|
155
|
+
#
|
156
|
+
|
157
|
+
# Saved? Persisted?
|
158
|
+
def saved?; !!@saved; end
|
159
|
+
|
160
|
+
# Unsaved? New record?
|
161
|
+
def unsaved?; !@saved; end
|
162
|
+
|
163
|
+
# Short cut for setting up a Mongocore::Query object
|
164
|
+
def mq(m, q = {}, o = {}, s = {})
|
165
|
+
Mongocore::Query.new(m, q, o, {:source => self}.merge(s))
|
166
|
+
end
|
167
|
+
|
168
|
+
# Short cut for simple query with cache buster
|
169
|
+
def single(s = {:cache => false})
|
170
|
+
mq(self.class, {:_id => @_id}, {}, s)
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
# # # # # # # # # # # # # # # #
|
175
|
+
# Read and write instance variables
|
176
|
+
#
|
177
|
+
|
178
|
+
# Get attribute if access
|
179
|
+
def read(key)
|
180
|
+
self.class.access.read?(key) ? read!(key) : nil
|
181
|
+
end
|
182
|
+
|
183
|
+
# Get attribute
|
184
|
+
def read!(key)
|
185
|
+
instance_variable_get("@#{key}")
|
186
|
+
end
|
187
|
+
|
188
|
+
# Set attribute if access
|
189
|
+
def write(key, val)
|
190
|
+
return nil unless self.class.access.write?(key)
|
191
|
+
|
192
|
+
# Convert to type as in schema yml
|
193
|
+
v = self.class.schema.convert(key, val)
|
194
|
+
|
195
|
+
# Record change for dirty attributes
|
196
|
+
read!(key).tap{|r| @changes[key] = r if v != r} if @changes
|
197
|
+
|
198
|
+
# Write attribute
|
199
|
+
write!(key, v)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Set attribute
|
203
|
+
def write!(key, v)
|
204
|
+
instance_variable_set("@#{key}", v)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Dynamically read or write attributes
|
208
|
+
def method_missing(name, *args, &block)
|
209
|
+
# Extract name and write mode
|
210
|
+
name =~ /([^=]+)(=)?/
|
211
|
+
|
212
|
+
# Write or read
|
213
|
+
if self.class.schema.keys.has_key?(key = $1.to_sym)
|
214
|
+
return write(key, args.first) if $2
|
215
|
+
return read(key)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Attributes changed?
|
219
|
+
return changes.has_key?($1.to_sym) if key =~ /(.+)_changed\?/
|
220
|
+
|
221
|
+
# Attributes was
|
222
|
+
return changes[$1.to_sym] if key =~ /(.+)_was/
|
223
|
+
|
224
|
+
# Pass if nothing found
|
225
|
+
super
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
# # # # # # # # # # # # # # #
|
232
|
+
# Class methods are mostly database lookups and filters
|
233
|
+
#
|
234
|
+
|
235
|
+
class_methods do
|
236
|
+
|
237
|
+
# Find, takes an id or a hash
|
238
|
+
def find(*args)
|
239
|
+
mq(self, *args)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Count
|
243
|
+
def count(*args)
|
244
|
+
find(*args).count
|
245
|
+
end
|
246
|
+
|
247
|
+
# First
|
248
|
+
def first(*args)
|
249
|
+
find(*args).first
|
250
|
+
end
|
251
|
+
|
252
|
+
# Last
|
253
|
+
def last
|
254
|
+
sort(:_id => -1).limit(1).first
|
255
|
+
end
|
256
|
+
|
257
|
+
# All
|
258
|
+
def all(*args)
|
259
|
+
find(*args).all
|
260
|
+
end
|
261
|
+
|
262
|
+
# Sort
|
263
|
+
def sort(o = {})
|
264
|
+
find({}, {}, :sort => o)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Limit
|
268
|
+
def limit(n = 1)
|
269
|
+
find({}, {}, :limit => n)
|
270
|
+
end
|
271
|
+
|
272
|
+
# # # # # # # # #
|
273
|
+
# After, before and validation filters
|
274
|
+
# Pass a method name as symbol or a block
|
275
|
+
#
|
276
|
+
# Possible events for after and before are :save, :update, :delete
|
277
|
+
#
|
278
|
+
|
279
|
+
# After
|
280
|
+
def after(*args, &block)
|
281
|
+
filters.after[args[0]] << (args[1] || block)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Before
|
285
|
+
def before(*args, &block)
|
286
|
+
filters.before[args[0]] << (args[1] || block)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Validate
|
290
|
+
def validate(*args, &block)
|
291
|
+
filters.validate << (args[0] || block)
|
292
|
+
end
|
293
|
+
|
294
|
+
# Short cut for setting up a Mongocore::Query object
|
295
|
+
def mq(*args)
|
296
|
+
Mongocore::Query.new(*args)
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Mongocore
|
2
|
+
class Filters
|
3
|
+
|
4
|
+
# # # # # # # #
|
5
|
+
# The Filters class is responsible for the before, after and validate filters.
|
6
|
+
#
|
7
|
+
|
8
|
+
# Accessors
|
9
|
+
attr_accessor :klass, :before, :after, :validate
|
10
|
+
|
11
|
+
# Init
|
12
|
+
def initialize(klass)
|
13
|
+
# Save model class
|
14
|
+
@klass = klass
|
15
|
+
|
16
|
+
# The before filters
|
17
|
+
@before = Hash.new{|h, k| h[k] = []}
|
18
|
+
|
19
|
+
# Add timestamp filters if enabled
|
20
|
+
[:save, :update].each{|f| @before[f] << :timestamps} if Mongocore.timestamps
|
21
|
+
|
22
|
+
# The after filters
|
23
|
+
@after = Hash.new{|h, k| h[k] = []}
|
24
|
+
|
25
|
+
# The validators
|
26
|
+
@validate = []
|
27
|
+
end
|
28
|
+
|
29
|
+
# Valid?
|
30
|
+
def valid?(m)
|
31
|
+
@validate.each{|k| call(k, m)}; m.errors.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Available filters are :save, :update, :delete
|
35
|
+
def run(m, f, key = nil)
|
36
|
+
send(f)[key].each{|k| call(k, m)}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Execute a proc or a method
|
40
|
+
def call(k, m)
|
41
|
+
k.is_a?(Proc) ? m.instance_eval(&k) : m.send(k)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Mongocore
|
2
|
+
class Query
|
3
|
+
|
4
|
+
# # # # # # # #
|
5
|
+
# The Query class keeps the cursor and handles the connection with the
|
6
|
+
# underlying MongoDB database. A new query is created every time you call
|
7
|
+
# find, sort, limit, count, update, scopes and associations.
|
8
|
+
#
|
9
|
+
# Every query can be chained, but only one find is ever done to the database,
|
10
|
+
# it's only the parameters that change.
|
11
|
+
#
|
12
|
+
|
13
|
+
attr_accessor :model, :collection, :colname, :query, :options, :store, :cache
|
14
|
+
|
15
|
+
# These options will be deleted before doing the find
|
16
|
+
def initialize(m, q = {}, o = {}, s = {})
|
17
|
+
# Support find passing a ID
|
18
|
+
q = {:_id => oid(q)} unless q.is_a?(Hash)
|
19
|
+
|
20
|
+
# Storing model class. The instance can be found in store[:source]
|
21
|
+
@model = m
|
22
|
+
|
23
|
+
# The model name is singular, the collection name is plural
|
24
|
+
@colname = "#{m.to_s.downcase}s".to_sym
|
25
|
+
|
26
|
+
# Storing the Mongo::Collection object
|
27
|
+
@collection = Mongocore.db[@colname]
|
28
|
+
|
29
|
+
# Storing query and options. Sort and limit is stored in options
|
30
|
+
s[:sort] ||= {}; s[:limit] ||= 0; s[:chain] ||= []; s[:source] ||= nil
|
31
|
+
@query, @options, @store = q, o, s
|
32
|
+
|
33
|
+
# Set up cache
|
34
|
+
@cache = Mongocore::Cache.new(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Find. Returns a Mongocore::Query
|
38
|
+
def find(q = {}, o = {}, s = {})
|
39
|
+
Mongocore::Query.new(@model, @query.merge(q), @options.merge(o), @store.merge(s))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Cursor
|
43
|
+
def cursor
|
44
|
+
@collection.find(@query, @options).sort(@store[:sort]).limit(@store[:limit])
|
45
|
+
end
|
46
|
+
|
47
|
+
# Update
|
48
|
+
def update(a)
|
49
|
+
# We do $set on non nil, $unset on nil
|
50
|
+
u = {
|
51
|
+
:$set => a.select{|k, v| !v.nil?}, :$unset => a.select{|k, v| v.nil?}
|
52
|
+
}.delete_if{|k, v| v.empty?}
|
53
|
+
|
54
|
+
# Update the collection
|
55
|
+
@collection.update_one(@query, u, :upsert => true)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Delete
|
59
|
+
def delete
|
60
|
+
@collection.delete_one(@query)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Count. Returns the number of documents as an integer
|
64
|
+
def count
|
65
|
+
counter || fetch(:count)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Check if there's a corresponding counter for this count
|
69
|
+
def counter(s = @store[:source], c = @store[:chain])
|
70
|
+
s.send(%{#{@colname}#{c.present? ? "_#{c.join('_')}" : ''}_count}) rescue nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return first document
|
74
|
+
def first(doc = nil)
|
75
|
+
(doc ||= fetch(:first)) ? @model.new(doc.to_hash) : nil
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return last document
|
79
|
+
def last
|
80
|
+
sort(:_id => -1).limit(1).first
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return all documents
|
84
|
+
def all
|
85
|
+
fetch(:to_a).map{|d| first(d)}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Fetch docs, pass type :first, :to_a or :count
|
89
|
+
def fetch(t)
|
90
|
+
cache.get(t) if Mongocore.cache
|
91
|
+
|
92
|
+
# Fetch from mongodb and add to cache
|
93
|
+
cursor.send(t).tap{|r| cache.set(t, r) if Mongocore.cache}
|
94
|
+
end
|
95
|
+
|
96
|
+
# Sort
|
97
|
+
def sort(o = {})
|
98
|
+
find(@query, options, @store.tap{store[:sort].merge!(o)})
|
99
|
+
end
|
100
|
+
|
101
|
+
# Limit
|
102
|
+
def limit(n = 1)
|
103
|
+
find(@query, @options, @store.tap{store[:limit] = n})
|
104
|
+
end
|
105
|
+
|
106
|
+
# Cache key
|
107
|
+
def key
|
108
|
+
@key ||= "#{@model}#{@query.sort}#{@options.sort}#{@store.values}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# String id to BSON::ObjectId, or create a new by passing nothing or nil
|
112
|
+
def oid(id = nil)
|
113
|
+
return id if id.is_a?(BSON::ObjectId)
|
114
|
+
return BSON::ObjectId.new if !id
|
115
|
+
BSON::ObjectId.from_string(id) rescue id
|
116
|
+
end
|
117
|
+
|
118
|
+
# Call and return the scope if it exists
|
119
|
+
def method_missing(name, *arguments, &block)
|
120
|
+
return @model.send(name, @query, @options, @store.tap{@store[:chain] << name}) if @model.schema.scopes.has_key?(name)
|
121
|
+
super
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Mongocore
|
2
|
+
class Schema
|
3
|
+
|
4
|
+
# # # # # # # #
|
5
|
+
# The Schema class is responsible for the schema handling.
|
6
|
+
#
|
7
|
+
|
8
|
+
# Accessors
|
9
|
+
attr_accessor :klass, :path, :schema, :meta, :accessors, :keys, :many, :scopes, :defaults
|
10
|
+
|
11
|
+
# Init
|
12
|
+
def initialize(klass)
|
13
|
+
# Store the document
|
14
|
+
@klass = klass
|
15
|
+
|
16
|
+
# Schema path
|
17
|
+
@path = File.join(Mongocore.schema, "#{@klass.to_s.downcase}.yml")
|
18
|
+
|
19
|
+
# Load schema
|
20
|
+
@schema = YAML.load(File.read(@path)).deep_symbolize_keys
|
21
|
+
|
22
|
+
# Meta
|
23
|
+
@meta = @schema[:meta] || {}
|
24
|
+
|
25
|
+
# Keys
|
26
|
+
@keys = @schema[:keys] || {}
|
27
|
+
|
28
|
+
# Accessors
|
29
|
+
(@accessors = @schema[:accessor] || []).each{|a| @klass.send(:attr_accessor, a)}
|
30
|
+
|
31
|
+
# Many
|
32
|
+
(@many = @schema[:many] || {}).each{|k, v| many(k, v)}
|
33
|
+
|
34
|
+
# Scopes
|
35
|
+
(@scopes = @schema[:scopes] || {}).each{|k, v| scope(k, v)}
|
36
|
+
|
37
|
+
# Defaults and foreign keys
|
38
|
+
@defaults = {}; @keys.each{|k, v| foreign(k, v); @defaults[k] = v[:default]}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get attributes that has these tags
|
42
|
+
def attributes(tags)
|
43
|
+
(tags[0] ? @keys.select{|k, v| v[:tags] & tags} : @keys).keys
|
44
|
+
end
|
45
|
+
|
46
|
+
# Convert type if val and schema type is set
|
47
|
+
def convert(key, val)
|
48
|
+
return nil if val.nil?
|
49
|
+
type = @keys[key][:type].to_sym rescue nil
|
50
|
+
return val if type.nil?
|
51
|
+
|
52
|
+
# Convert to the same type as in the schema
|
53
|
+
return val.to_i if type == :integer
|
54
|
+
return val.to_f if type == :float
|
55
|
+
return !!val if type == :boolean
|
56
|
+
if type == :object_id and !val.is_a?(BSON::ObjectId)
|
57
|
+
return BSON::ObjectId.from_string(val) rescue nil
|
58
|
+
end
|
59
|
+
val
|
60
|
+
end
|
61
|
+
|
62
|
+
# # # # # # # # #
|
63
|
+
# Templates for foreign key, many-associations and scopes.
|
64
|
+
#
|
65
|
+
|
66
|
+
# Foreign keys
|
67
|
+
def foreign(key, data)
|
68
|
+
return if key !~ /(.+)_id/
|
69
|
+
t = %Q{
|
70
|
+
def #{$1}
|
71
|
+
@#{$1} ||= mq(#{$1.capitalize}, :_id => @#{key}).first
|
72
|
+
end
|
73
|
+
|
74
|
+
def #{$1}=(m)
|
75
|
+
@#{key} = m._id
|
76
|
+
@#{$1} = m
|
77
|
+
end
|
78
|
+
}
|
79
|
+
@klass.class_eval t
|
80
|
+
end
|
81
|
+
|
82
|
+
# Many
|
83
|
+
def many(key, data)
|
84
|
+
t = %Q{
|
85
|
+
def #{key}
|
86
|
+
mq(#{key[0..-2].capitalize}, {:#{@klass.to_s.downcase}_id => @_id}, {}, :source => self)
|
87
|
+
end
|
88
|
+
}
|
89
|
+
@klass.class_eval t
|
90
|
+
end
|
91
|
+
|
92
|
+
# Set up scope and insert it
|
93
|
+
def scope(key, data)
|
94
|
+
# Extract the parameters
|
95
|
+
pm = data.delete(:params) || []
|
96
|
+
|
97
|
+
# Replace data if we are using parameters
|
98
|
+
d = %{#{data}}
|
99
|
+
pm.each do |a|
|
100
|
+
d.scan(%r{(=>"(#{a})(\.[a-z0-9]+)?")}).each do |n|
|
101
|
+
d.gsub!(n[0], %{=>#{n[1]}#{n[2]}})
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Define the scope method so we can call it
|
106
|
+
j = pm.any? ? %{#{pm.join(', ')},} : ''
|
107
|
+
t = %Q{
|
108
|
+
def #{key}(#{j} q = {}, o = {}, s = {})
|
109
|
+
mq(self, q.merge(#{d}), o, {:scope => [:#{key}]}.merge(s))
|
110
|
+
end
|
111
|
+
}
|
112
|
+
@klass.instance_eval t
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
data/lib/mongocore.rb
CHANGED
data/models/model.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
class Model
|
2
|
+
include Mongocore::Document
|
3
|
+
|
4
|
+
# Just define a validate method and call it when needed
|
5
|
+
# Use the errors hash to add your errors to it
|
6
|
+
validate do
|
7
|
+
errors[:duration] << 'duration must be greater than 0' if duration and duration < 1
|
8
|
+
errors[:goal] << 'you need a higher goal' if goal and goal < 5
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :list
|
12
|
+
|
13
|
+
before :save do
|
14
|
+
(@list ||= []) << 'before_save'
|
15
|
+
end
|
16
|
+
|
17
|
+
before :update do
|
18
|
+
(@list ||= []) << 'before_update'
|
19
|
+
end
|
20
|
+
|
21
|
+
before :delete do
|
22
|
+
(@list ||= []) << 'before_delete'
|
23
|
+
end
|
24
|
+
|
25
|
+
after :save do
|
26
|
+
(@list ||= []) << 'after_save'
|
27
|
+
end
|
28
|
+
|
29
|
+
after :update do
|
30
|
+
(@list ||= []) << 'after_update'
|
31
|
+
end
|
32
|
+
|
33
|
+
after :delete do
|
34
|
+
(@list ||= []) << 'after_delete'
|
35
|
+
end
|
36
|
+
|
37
|
+
# Save, update, delete
|
38
|
+
# before :delete, :hello
|
39
|
+
# after(:delete){ puts "Hello" }
|
40
|
+
|
41
|
+
# def hello
|
42
|
+
# puts "HELLO"
|
43
|
+
# end
|
44
|
+
end
|
data/models/parent.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
class Parent
|
2
|
+
include Mongocore::Document
|
3
|
+
|
4
|
+
attr_accessor :list
|
5
|
+
|
6
|
+
before :save do
|
7
|
+
(@list ||= []) << 'before_save'
|
8
|
+
end
|
9
|
+
|
10
|
+
before :update do
|
11
|
+
(@list ||= []) << 'before_update'
|
12
|
+
end
|
13
|
+
|
14
|
+
before :delete do
|
15
|
+
(@list ||= []) << 'before_delete'
|
16
|
+
end
|
17
|
+
|
18
|
+
after :save do
|
19
|
+
(@list ||= []) << 'after_save'
|
20
|
+
end
|
21
|
+
|
22
|
+
after :update do
|
23
|
+
(@list ||= []) << 'after_update'
|
24
|
+
end
|
25
|
+
|
26
|
+
after :delete do
|
27
|
+
(@list ||= []) << 'after_delete'
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/mongocore.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'mongocore'
|
3
|
+
s.version = '0.1.1'
|
4
|
+
s.date = '2017-01-05'
|
5
|
+
s.summary = "MongoDB ORM implementation on top of the Ruby MongoDB driver"
|
6
|
+
s.description = "Does validations, associations, scopes, filters, counter cache, request cache, and nested queries. Using a YAML schema file, which supports default values, data types, and security levels for each key."
|
7
|
+
s.authors = ["Fugroup Limited"]
|
8
|
+
s.email = 'mail@fugroup.net'
|
9
|
+
|
10
|
+
s.add_runtime_dependency 'mongo', '~> 2.2'
|
11
|
+
s.add_runtime_dependency 'request_store', '>= 0'
|
12
|
+
s.add_runtime_dependency 'activesupport', '>= 0'
|
13
|
+
s.add_development_dependency 'futest', '>= 0'
|
14
|
+
|
15
|
+
s.homepage = 'https://github.com/fugroup/mongocore'
|
16
|
+
s.license = 'MIT'
|
17
|
+
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
s.files = `git ls-files -z`.split("\x0").reject do |f|
|
20
|
+
f.match(%r{^(test|spec|features)/})
|
21
|
+
end
|
22
|
+
end
|