active_aggregate 0.0.2 → 0.0.3
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/README.md +100 -1
- data/lib/active_aggregate/concern.rb +40 -11
- data/lib/active_aggregate/relation.rb +28 -5
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ea7dac3e2a7ec25fe7a0626f180c744e56997e950097def32c6d5ffa3d834bc
|
4
|
+
data.tar.gz: 6960dd0b37fada8e6ea18b6b1513cf285a865472349403f8411bf8a7559654a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 728e4ebf029dfe740e67d3caa5bb8eb1ba720a02c7af329baee1285a6574c6ac560b1499c105056203bd57f9d87de59eb09d70bc16b61df8a89b978b16608fa2
|
7
|
+
data.tar.gz: e13acc34d877094783c344a34674263d1a629d25e9e9eab73febb02dd93ec5b0596284b01afc923d98ff5075b8e187ac495e33c6f6ce9588404dd276a647dd01
|
data/README.md
CHANGED
@@ -1,10 +1,109 @@
|
|
1
1
|
# active_aggregate
|
2
2
|
|
3
3
|
- active_aggregate is a little helper support to queries by mongoDB aggregate more easily.
|
4
|
-
- A toolkit for building queries like ActiveRelation. Rich support for more flexible merge
|
4
|
+
- A toolkit for building queries like ActiveRelation. Rich support for more flexible merge conditions, states
|
5
5
|
|
6
6
|
## Getting started
|
7
7
|
|
8
8
|
```ruby
|
9
9
|
gem install active_aggregate
|
10
10
|
```
|
11
|
+
|
12
|
+
## Requirements
|
13
|
+
- mongoid >= 5.0.1
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# models/user.rb
|
19
|
+
class User
|
20
|
+
include Mongoid::Document
|
21
|
+
|
22
|
+
belongs_to :school
|
23
|
+
belongs_to :branch
|
24
|
+
|
25
|
+
scope :active, -> { where(status: :active) }
|
26
|
+
scope :by_status, ->(status) { where(status: status) }
|
27
|
+
end
|
28
|
+
|
29
|
+
class Query
|
30
|
+
include ActiveAggregate::Concern
|
31
|
+
end
|
32
|
+
|
33
|
+
class UserQuery < Query
|
34
|
+
define_for User
|
35
|
+
|
36
|
+
# you can use `criteria.active` instead of `User.active`
|
37
|
+
scope :load_active_user_names, criteria: User.active,
|
38
|
+
project: {
|
39
|
+
id: '$_id',
|
40
|
+
name: {
|
41
|
+
'$concat': [
|
42
|
+
'$first_name',
|
43
|
+
' ',
|
44
|
+
'$last_name',
|
45
|
+
]
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
scope :not_deleted, criteria: User.where(deleted_at: nil)
|
50
|
+
scope :load_user_ids,
|
51
|
+
->(status:, school_id_branch_ids:) do
|
52
|
+
where(status: status).pipeline(
|
53
|
+
'$match': {
|
54
|
+
'school_id_branch_id': {
|
55
|
+
'$in': school_id_branch_ids,
|
56
|
+
}
|
57
|
+
}
|
58
|
+
)
|
59
|
+
|
60
|
+
# it avaiable to use like
|
61
|
+
# query_criteria(User.by_status(status)).pipeline([
|
62
|
+
# '$match': {
|
63
|
+
# 'school_id_branch_id': {
|
64
|
+
# '$in': school_id_branch_ids,
|
65
|
+
# }
|
66
|
+
# }
|
67
|
+
# ])
|
68
|
+
|
69
|
+
end,
|
70
|
+
project: {
|
71
|
+
id: '$_id',
|
72
|
+
school_id_branch_id: {
|
73
|
+
'$concatArrays': [
|
74
|
+
['$school_id'],
|
75
|
+
['$branch_id'],
|
76
|
+
]
|
77
|
+
}
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
# another way
|
82
|
+
# class Query
|
83
|
+
# include ActiveAggregate::Concern
|
84
|
+
|
85
|
+
# # On children class it will remove [suffix] at end of class name to get model name then you can skip call define_for each all of Query class
|
86
|
+
# # [suffix] have default value is Query
|
87
|
+
# with_suffix
|
88
|
+
# # with_suffix suffix: :Query
|
89
|
+
# end
|
90
|
+
|
91
|
+
# class UserQuery < Query
|
92
|
+
# end
|
93
|
+
|
94
|
+
|
95
|
+
UserQuery.not_deleted.load_user_ids.where(:created_at.lt => Time.current)
|
96
|
+
```
|
97
|
+
|
98
|
+
- `scope` support define for:
|
99
|
+
- criteria: as `Mongoid::Criteria` it will place at first of pipeline if given, by default it is default scope
|
100
|
+
- group: as object, can be merge throw all queries.
|
101
|
+
- project: as object, will be replace throw merge ActiveAggregate::Relation
|
102
|
+
- sort, limit: as object, will be replace throw merge ActiveAggregate::Relation.
|
103
|
+
- pipeline: as Array will place end of pre-pipeline if given, merge with previous pipeline by concat 2 array
|
104
|
+
`scope` will generate pipeline to use with aggregate with states order by:
|
105
|
+
- State 1 is `$match` use`criteria` selector if selector present
|
106
|
+
- state 2 is `$group` if `group` given
|
107
|
+
- state 3 is `$project` if `project` given
|
108
|
+
- state 3 is `$limit` if `limit` given
|
109
|
+
- pipeline will be place from here
|
@@ -2,28 +2,22 @@ module ActiveAggregate::Concern
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
class_methods do
|
5
|
-
attr_reader :model, :criteria
|
6
|
-
|
7
5
|
delegate :query, :query_criteria, :group, :project, :pipeline, :group_by, :to_a,
|
8
|
-
:where, :in, :any_off, :all_of,
|
6
|
+
:where, :in, :where_in, :any_off, :all_of, :limit, :sort,
|
9
7
|
to: :all
|
10
8
|
|
11
|
-
def define_for(model)
|
12
|
-
@model = model
|
13
|
-
@criteria = model.all
|
14
|
-
end
|
15
|
-
|
16
9
|
def scope(name, *options)
|
17
|
-
|
10
|
+
required_model!
|
18
11
|
scope_name = name.to_sym
|
19
|
-
scopes[scope_name] = ActiveAggregate::Relation.new(self, *options)
|
12
|
+
scopes[scope_name] = ActiveAggregate::Relation.new(self, *options).tap { |relation| relation.cacheable = false }
|
20
13
|
singleton_class.send(:define_method, scope_name) do |*args|
|
14
|
+
scopes[scope_name] = ActiveAggregate::Relation.new(self, *options)
|
21
15
|
return scopes[scope_name].generate(*args)
|
22
16
|
end
|
23
17
|
end
|
24
18
|
|
25
19
|
def all
|
26
|
-
|
20
|
+
required_model!
|
27
21
|
ActiveAggregate::Relation.new(self).generate
|
28
22
|
end
|
29
23
|
|
@@ -51,5 +45,40 @@ module ActiveAggregate::Concern
|
|
51
45
|
def scopes
|
52
46
|
@scopes ||= {}
|
53
47
|
end
|
48
|
+
|
49
|
+
def model
|
50
|
+
@model ||= load_model
|
51
|
+
end
|
52
|
+
|
53
|
+
def criteria
|
54
|
+
@criteria ||= load_model.all
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def define_for(model)
|
60
|
+
@criteria = model.all
|
61
|
+
@model = model
|
62
|
+
end
|
63
|
+
|
64
|
+
def with_suffix(suffix: :Query)
|
65
|
+
singleton_class.send(:define_method, :suffix) do
|
66
|
+
suffix
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def load_model
|
71
|
+
@model || define_for_model_by_remove_suffix
|
72
|
+
end
|
73
|
+
|
74
|
+
def define_for_model_by_remove_suffix
|
75
|
+
return if !defined?(suffix) || suffix.nil?
|
76
|
+
model_name = name[0..(name.length - suffix.length - 1)]
|
77
|
+
define_for(model_name.constantize)
|
78
|
+
end
|
79
|
+
|
80
|
+
def required_model!
|
81
|
+
raise TypeError, 'set defined scope for model first' unless model
|
82
|
+
end
|
54
83
|
end
|
55
84
|
end
|
@@ -2,9 +2,11 @@ class ActiveAggregate::Relation
|
|
2
2
|
include ActiveSupport::Concern
|
3
3
|
|
4
4
|
attr_reader :scope_class, :body
|
5
|
+
attr_accessor :cacheable
|
5
6
|
|
6
7
|
def initialize(scope_class, body = nil, options = {})
|
7
8
|
@scope_class = scope_class
|
9
|
+
@cacheable = true
|
8
10
|
if body.respond_to?(:call)
|
9
11
|
init_default_value(options)
|
10
12
|
@body = body
|
@@ -15,9 +17,9 @@ class ActiveAggregate::Relation
|
|
15
17
|
|
16
18
|
delegate :scope?, :scope_names, :model, to: :scope_class
|
17
19
|
delegate :selector, to: :criteria
|
18
|
-
delegate :
|
20
|
+
delegate :count, :first, to: :aggregate
|
19
21
|
delegate :select, :find, :last, :group_by, :each_with_object, :each,
|
20
|
-
:map, :reduce, :reject,
|
22
|
+
:map, :reduce, :reject, :to_json,
|
21
23
|
to: :to_a
|
22
24
|
|
23
25
|
def query(options)
|
@@ -80,10 +82,10 @@ class ActiveAggregate::Relation
|
|
80
82
|
def generate_execute_pipeline(select_all: false, aggregate: {})
|
81
83
|
merge(aggregate) if aggregate.present?
|
82
84
|
[].tap do |execute_pipeline|
|
83
|
-
execute_pipeline << { '$match':
|
85
|
+
execute_pipeline << { '$match': selector } if selector.present?
|
84
86
|
execute_pipeline << { '$group': @group } if @group.present?
|
85
87
|
execute_pipeline << { '$sort': @sort } if @sort.present?
|
86
|
-
execute_pipeline << { '$project':
|
88
|
+
execute_pipeline << { '$project': generate_project } if select_all || @project.present?
|
87
89
|
execute_pipeline << { '$limit': @limit } if @limit.present?
|
88
90
|
execute_pipeline.push(*@pipeline)
|
89
91
|
end
|
@@ -107,6 +109,13 @@ class ActiveAggregate::Relation
|
|
107
109
|
model.collection.aggregate(generate_execute_pipeline(options), *args)
|
108
110
|
end
|
109
111
|
|
112
|
+
def to_a
|
113
|
+
return @as_array if cacheable && @as_array
|
114
|
+
aggregate.to_a.tap { |array| @as_array ||= array if cacheable }
|
115
|
+
end
|
116
|
+
|
117
|
+
alias load to_a
|
118
|
+
|
110
119
|
def add_pipeline(*stages)
|
111
120
|
@pipeline.push(*stages.flatten)
|
112
121
|
end
|
@@ -136,6 +145,20 @@ class ActiveAggregate::Relation
|
|
136
145
|
query_criteria(model.in(*args))
|
137
146
|
end
|
138
147
|
|
148
|
+
def pluck(*fields)
|
149
|
+
fields = Array.wrap(fields).map(&:to_s)
|
150
|
+
to_a.each_with_object([]) do |doc, result|
|
151
|
+
record = if fields.size == 1
|
152
|
+
doc[fields.first]
|
153
|
+
else
|
154
|
+
doc.slice(*fields).values
|
155
|
+
end
|
156
|
+
result << record unless record.nil?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
alias where_in in
|
161
|
+
|
139
162
|
def any_of(*args)
|
140
163
|
query_criteria(model.any_of(*args))
|
141
164
|
end
|
@@ -164,7 +187,7 @@ class ActiveAggregate::Relation
|
|
164
187
|
|
165
188
|
private
|
166
189
|
|
167
|
-
def init_default_value(criteria:
|
190
|
+
def init_default_value(criteria: model.all, pipeline: [], group: nil, project: nil, sort: nil, limit: nil)
|
168
191
|
@criteria = format_criteria(criteria)
|
169
192
|
@pipeline = pipeline.present? ? pipeline : []
|
170
193
|
@group = group.present? ? group : {}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_aggregate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Phan Quang Tien
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongoid
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 5.0.1
|
27
27
|
description: A toolkit for building queries like ActiveRelation. Rich support for
|
28
|
-
more flexible merge
|
28
|
+
more flexible merge conditions, states
|
29
29
|
email:
|
30
30
|
executables: []
|
31
31
|
extensions: []
|
@@ -56,5 +56,5 @@ rubygems_version: 3.0.6
|
|
56
56
|
signing_key:
|
57
57
|
specification_version: 4
|
58
58
|
summary: active_aggregate is a little helper support to queries by mongoDB aggregate
|
59
|
-
more easily
|
59
|
+
more easily.
|
60
60
|
test_files: []
|