esse-active_record 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 +7 -0
- data/.rubocop.yml +35 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +128 -0
- data/LICENSE.txt +21 -0
- data/README.md +226 -0
- data/Rakefile +4 -0
- data/lib/esse/active_record/collection.rb +129 -0
- data/lib/esse/active_record/hooks.rb +210 -0
- data/lib/esse/active_record/model.rb +77 -0
- data/lib/esse/active_record/version.rb +7 -0
- data/lib/esse/active_record.rb +14 -0
- data/lib/esse/plugins/active_record.rb +49 -0
- data/sig/esse/active_record.rbs +6 -0
- metadata +253 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: eeb304ba4915091f1b51addb850fc9161b1c415dcf61fdd9f0b189b43b7b4ac2
|
|
4
|
+
data.tar.gz: 52182a2e70cdb56f4658ef4ba515a86267ccc4b48a5e1ef5f2895ca192aca6be
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 24eb997cfdb498b4f8725e405aef700c22dd0a31c5d08b8092a923022543e06d9235f63c75a0d8099f06d1cd69fd00a6ecd4a7d8326bd807fe609fb0b295ad93
|
|
7
|
+
data.tar.gz: e770dcd174126061bd938979ea4d36f9c761a3b4efb6f8360a73642cc423efa57c9c22a9ac637b5453ac4ab497f7cb5e09509b0e60608989b9114ab03099bd5e
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
inherit_mode:
|
|
2
|
+
merge:
|
|
3
|
+
- Exclude
|
|
4
|
+
|
|
5
|
+
require:
|
|
6
|
+
- rubocop-performance
|
|
7
|
+
- rubocop-rspec
|
|
8
|
+
- standard/cop/block_single_line_braces
|
|
9
|
+
|
|
10
|
+
inherit_gem:
|
|
11
|
+
standard: config/base.yml
|
|
12
|
+
|
|
13
|
+
AllCops:
|
|
14
|
+
TargetRubyVersion: 2.6
|
|
15
|
+
SuggestExtensions: false
|
|
16
|
+
Exclude:
|
|
17
|
+
- "db/**/*"
|
|
18
|
+
- "tmp/**/*"
|
|
19
|
+
- "vendor/**/*"
|
|
20
|
+
|
|
21
|
+
Layout/SpaceInsideHashLiteralBraces:
|
|
22
|
+
Enabled: false
|
|
23
|
+
|
|
24
|
+
Style/TrailingCommaInArguments:
|
|
25
|
+
Enabled: false
|
|
26
|
+
|
|
27
|
+
Style/TrailingCommaInArrayLiteral:
|
|
28
|
+
Enabled: false
|
|
29
|
+
|
|
30
|
+
Style/TrailingCommaInHashLiteral:
|
|
31
|
+
Enabled: false
|
|
32
|
+
|
|
33
|
+
Style/StringLiterals:
|
|
34
|
+
Enabled: true
|
|
35
|
+
EnforcedStyle: single_quotes
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
GIT
|
|
2
|
+
remote: https://github.com/marcosgz/esse.git
|
|
3
|
+
revision: fa4eb4d8c54aae89c388415585b4b27c75b31198
|
|
4
|
+
branch: master
|
|
5
|
+
specs:
|
|
6
|
+
esse (0.2.1)
|
|
7
|
+
multi_json
|
|
8
|
+
thor (>= 0.19)
|
|
9
|
+
|
|
10
|
+
PATH
|
|
11
|
+
remote: .
|
|
12
|
+
specs:
|
|
13
|
+
esse-active_record (0.1.1)
|
|
14
|
+
activerecord (>= 4.2, < 8)
|
|
15
|
+
esse
|
|
16
|
+
|
|
17
|
+
GEM
|
|
18
|
+
remote: https://rubygems.org/
|
|
19
|
+
specs:
|
|
20
|
+
activemodel (5.2.8.1)
|
|
21
|
+
activesupport (= 5.2.8.1)
|
|
22
|
+
activerecord (5.2.8.1)
|
|
23
|
+
activemodel (= 5.2.8.1)
|
|
24
|
+
activesupport (= 5.2.8.1)
|
|
25
|
+
arel (>= 9.0)
|
|
26
|
+
activesupport (5.2.8.1)
|
|
27
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
28
|
+
i18n (>= 0.7, < 2)
|
|
29
|
+
minitest (~> 5.1)
|
|
30
|
+
tzinfo (~> 1.1)
|
|
31
|
+
addressable (2.8.0)
|
|
32
|
+
public_suffix (>= 2.0.2, < 5.0)
|
|
33
|
+
arel (9.0.0)
|
|
34
|
+
ast (2.4.2)
|
|
35
|
+
awesome_print (1.9.2)
|
|
36
|
+
coderay (1.1.3)
|
|
37
|
+
concurrent-ruby (1.1.10)
|
|
38
|
+
crack (0.4.5)
|
|
39
|
+
rexml
|
|
40
|
+
diff-lcs (1.5.0)
|
|
41
|
+
dotenv (2.7.6)
|
|
42
|
+
hashdiff (1.0.1)
|
|
43
|
+
i18n (1.12.0)
|
|
44
|
+
concurrent-ruby (~> 1.0)
|
|
45
|
+
method_source (1.0.0)
|
|
46
|
+
minitest (5.16.2)
|
|
47
|
+
multi_json (1.15.0)
|
|
48
|
+
parallel (1.22.1)
|
|
49
|
+
parser (3.1.2.0)
|
|
50
|
+
ast (~> 2.4.1)
|
|
51
|
+
pry (0.14.1)
|
|
52
|
+
coderay (~> 1.1)
|
|
53
|
+
method_source (~> 1.0)
|
|
54
|
+
public_suffix (4.0.7)
|
|
55
|
+
rainbow (3.1.1)
|
|
56
|
+
rake (12.3.3)
|
|
57
|
+
regexp_parser (2.5.0)
|
|
58
|
+
rexml (3.2.5)
|
|
59
|
+
rspec (3.11.0)
|
|
60
|
+
rspec-core (~> 3.11.0)
|
|
61
|
+
rspec-expectations (~> 3.11.0)
|
|
62
|
+
rspec-mocks (~> 3.11.0)
|
|
63
|
+
rspec-core (3.11.0)
|
|
64
|
+
rspec-support (~> 3.11.0)
|
|
65
|
+
rspec-expectations (3.11.0)
|
|
66
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
67
|
+
rspec-support (~> 3.11.0)
|
|
68
|
+
rspec-mocks (3.11.1)
|
|
69
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
70
|
+
rspec-support (~> 3.11.0)
|
|
71
|
+
rspec-support (3.11.0)
|
|
72
|
+
rubocop (1.29.1)
|
|
73
|
+
parallel (~> 1.10)
|
|
74
|
+
parser (>= 3.1.0.0)
|
|
75
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
76
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
77
|
+
rexml (>= 3.2.5, < 4.0)
|
|
78
|
+
rubocop-ast (>= 1.17.0, < 2.0)
|
|
79
|
+
ruby-progressbar (~> 1.7)
|
|
80
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
|
81
|
+
rubocop-ast (1.18.0)
|
|
82
|
+
parser (>= 3.1.1.0)
|
|
83
|
+
rubocop-performance (1.13.3)
|
|
84
|
+
rubocop (>= 1.7.0, < 2.0)
|
|
85
|
+
rubocop-ast (>= 0.4.0)
|
|
86
|
+
rubocop-rspec (2.11.1)
|
|
87
|
+
rubocop (~> 1.19)
|
|
88
|
+
ruby-progressbar (1.11.0)
|
|
89
|
+
sqlite3 (1.3.13)
|
|
90
|
+
standard (1.12.1)
|
|
91
|
+
rubocop (= 1.29.1)
|
|
92
|
+
rubocop-performance (= 1.13.3)
|
|
93
|
+
thor (1.2.1)
|
|
94
|
+
thread_safe (0.3.6)
|
|
95
|
+
tzinfo (1.2.10)
|
|
96
|
+
thread_safe (~> 0.1)
|
|
97
|
+
unicode-display_width (2.2.0)
|
|
98
|
+
webmock (3.14.0)
|
|
99
|
+
addressable (>= 2.8.0)
|
|
100
|
+
crack (>= 0.3.2)
|
|
101
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
102
|
+
webrick (1.7.0)
|
|
103
|
+
yard (0.9.28)
|
|
104
|
+
webrick (~> 1.7.0)
|
|
105
|
+
|
|
106
|
+
PLATFORMS
|
|
107
|
+
x86_64-darwin-19
|
|
108
|
+
x86_64-linux
|
|
109
|
+
|
|
110
|
+
DEPENDENCIES
|
|
111
|
+
activerecord (~> 5.2)
|
|
112
|
+
awesome_print
|
|
113
|
+
dotenv
|
|
114
|
+
esse!
|
|
115
|
+
esse-active_record!
|
|
116
|
+
pry
|
|
117
|
+
rake (~> 12.3)
|
|
118
|
+
rspec (~> 3.0)
|
|
119
|
+
rubocop (~> 1.20)
|
|
120
|
+
rubocop-performance (~> 1.11, >= 1.11.5)
|
|
121
|
+
rubocop-rspec (~> 2.4)
|
|
122
|
+
sqlite3 (~> 1.3.6)
|
|
123
|
+
standard (~> 1.3)
|
|
124
|
+
webmock (~> 3.14)
|
|
125
|
+
yard (~> 0.9.20)
|
|
126
|
+
|
|
127
|
+
BUNDLED WITH
|
|
128
|
+
2.3.21
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Marcos G. Zimmermann
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Esse ActiveRecord Plugin
|
|
2
|
+
|
|
3
|
+
This gem is a [esse](https://github.com/marcosgz/esse) plugin for the ActiveRecord ORM. It provides a set of methods to simplify implementation of ActiveRecord models as datasource of esse indexes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'esse-active_record'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle install
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install esse-active_record
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Add the `:active_record` plugin and configure the `collection` with the ActiveRecord model you want to use.
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
class UsersIndex < Esse::Index
|
|
27
|
+
plugin :active_record
|
|
28
|
+
|
|
29
|
+
repository :user do
|
|
30
|
+
collection ::User
|
|
31
|
+
serializer # ...
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Using multiple repositories is also possible:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
class UsersIndex < Esse::Index
|
|
40
|
+
plugin :active_record
|
|
41
|
+
|
|
42
|
+
repository :account do
|
|
43
|
+
collection ::Account
|
|
44
|
+
serializer # ...
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
repository :admin do
|
|
48
|
+
collection ::User.where(admin: true)
|
|
49
|
+
serializer # ...
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Collection Scope
|
|
55
|
+
It's also possible to specify custom scopes to the repository collection to be used to import data to the index:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
class UsersIndex < Esse::Index
|
|
59
|
+
plugin :active_record
|
|
60
|
+
|
|
61
|
+
repository :user do
|
|
62
|
+
collection ::User do
|
|
63
|
+
scope :active, -> { where(active: true) }
|
|
64
|
+
scope :role, ->(role) { where(role: role) }
|
|
65
|
+
end
|
|
66
|
+
serializer # ...
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Import data using the scopes
|
|
71
|
+
# > UsersIndex.elasticsearch.import(context: { active: true, role: 'admin' })
|
|
72
|
+
#
|
|
73
|
+
# Streaming data using the scopes
|
|
74
|
+
# > UsersIndex.documents(active: true, role: 'admin').first
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Collection Batch Context
|
|
78
|
+
|
|
79
|
+
Assume that you have a collection of orders and you want to also include the customer data that lives in a external system. To avoid making a request for each order, you can use the `batch_context` to fetch the data in batches and make it available in the serializer context.
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
class OrdersIndex < Esse::Index
|
|
83
|
+
plugin :active_record
|
|
84
|
+
|
|
85
|
+
repository :order do
|
|
86
|
+
collection ::Order do
|
|
87
|
+
batch_context :customers do |orders, **_existing_context|
|
|
88
|
+
# The return value will be available in the serializer context
|
|
89
|
+
# { customers: <value returned from this block> }
|
|
90
|
+
ExternalSystem::Customer.find_all_by_ids(orders.map(&:customer_id)).index_by(&:id) # => { 1 => <Customer>, 2 => <Customer> }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
serializer do |order, customers: [], **_|
|
|
94
|
+
customer = customers[order.customer_id]
|
|
95
|
+
{
|
|
96
|
+
id: order.id,
|
|
97
|
+
customer: {
|
|
98
|
+
id: customer&.id,
|
|
99
|
+
name: customer&.name
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
For active record associations, you can define the repository collection by eager loading the associations as usual:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
|
|
111
|
+
class OrdersIndex < Esse::Index
|
|
112
|
+
plugin :active_record
|
|
113
|
+
|
|
114
|
+
repository :order do
|
|
115
|
+
collection ::Order.includes(:customer)
|
|
116
|
+
serializer do |order, **_|
|
|
117
|
+
{
|
|
118
|
+
id: order.id,
|
|
119
|
+
customer: {
|
|
120
|
+
id: order.customer&.id,
|
|
121
|
+
name: order.customer&.name
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Data Streaming Optionsou
|
|
130
|
+
|
|
131
|
+
As default the active record support 3 streaming options:
|
|
132
|
+
* `batch_size`: the number of documents to be streamed in each batch. Default is 1000;
|
|
133
|
+
* `start`: the primary key value to start from, inclusive of the value;
|
|
134
|
+
* `finish`: the primary key value to end at, inclusive of the value;
|
|
135
|
+
|
|
136
|
+
This is useful when you want to import simultaneous data. You can make one process import all records between 1 and 10,000, and another from 10,000 and beyond
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
UsersIndex.elasticsearch.import(context: { start: 1, finish: 10000, batch_size: 500 })
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The default valueof `batch_size` can be also defined in the `collection` configuration:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
class UsersIndex < Esse::Index
|
|
146
|
+
plugin :active_record
|
|
147
|
+
|
|
148
|
+
repository :user do
|
|
149
|
+
collection ::User, batch_size: 500
|
|
150
|
+
serializer # ...
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Indexing Callbacks
|
|
156
|
+
|
|
157
|
+
The `index_callbacks` callback can be used to automaitcally index or delete documents after commit on create/update/destroy events.
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
class UsersIndex < Esse::Index
|
|
161
|
+
plugin :active_record
|
|
162
|
+
|
|
163
|
+
repository :user, const: true do
|
|
164
|
+
collection ::User
|
|
165
|
+
serializer # ...
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
class User < ApplicationRecord
|
|
171
|
+
belongs_to :organization
|
|
172
|
+
|
|
173
|
+
# Using a index and repository as argument. Note that the index name is used instead of the
|
|
174
|
+
# of the constant name. it's so because index and model depends on each other should result in
|
|
175
|
+
# circular dependencies issues.
|
|
176
|
+
index_callbacks 'users_index:user'
|
|
177
|
+
# Using a block to direct a different object to be indexed
|
|
178
|
+
index_callbacks('organizations') { user.organization } # The `_index` suffix and repo name is optional on the index name
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Callbacks can also be disabled/enabled globally:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
Esse::ActiveRecord::Hoods.disable!
|
|
186
|
+
Esse::ActiveRecord::Hoods.enable!
|
|
187
|
+
Esse::ActiveRecord::Hoods.without_indexing do
|
|
188
|
+
10.times { User.create! }
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
or by some specific list of index or index's repository
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
Esse::ActiveRecord::Hoods.disable!(UsersIndex.repo)
|
|
196
|
+
Esse::ActiveRecord::Hoods.enable!(UsersIndex.repo)
|
|
197
|
+
Esse::ActiveRecord::Hoods.without_indexing(AccountsIndex UsersIndex.repo, ) do
|
|
198
|
+
10.times { User.create! }
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
or by the model that the callback is configured
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
User.without_indexing do
|
|
206
|
+
10.times { User.create! }
|
|
207
|
+
end
|
|
208
|
+
User.without_indexing(AccountsIndex) do
|
|
209
|
+
10.times { User.create! }
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
## Development
|
|
215
|
+
|
|
216
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake none` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
217
|
+
|
|
218
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
219
|
+
|
|
220
|
+
## Contributing
|
|
221
|
+
|
|
222
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/marcosgz/esse-active_record.
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module Esse
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
class Collection
|
|
4
|
+
include Enumerable
|
|
5
|
+
|
|
6
|
+
# The model class or the relation to be used as the scope
|
|
7
|
+
# @return [Proc]
|
|
8
|
+
class_attribute :base_scope
|
|
9
|
+
|
|
10
|
+
# The number of records to be returned in each batch
|
|
11
|
+
# @return [Integer]
|
|
12
|
+
class_attribute :batch_size
|
|
13
|
+
|
|
14
|
+
# Hash with the custom scopes defined on the model
|
|
15
|
+
# @return [Hash]
|
|
16
|
+
class_attribute :scopes
|
|
17
|
+
self.scopes = {}
|
|
18
|
+
|
|
19
|
+
# The hash with the contexts as key and the transformer proc as value
|
|
20
|
+
# @return [Hash]
|
|
21
|
+
class_attribute :batch_contexts
|
|
22
|
+
self.batch_contexts = {}
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
def inspect
|
|
26
|
+
return super unless self < Esse::ActiveRecord::Collection
|
|
27
|
+
return super unless base_scope
|
|
28
|
+
|
|
29
|
+
format('#<Esse::ActiveRecord::Collection__%s>', model)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def model
|
|
33
|
+
raise(NotImplementedError, "No model defined for #{self}") unless base_scope
|
|
34
|
+
|
|
35
|
+
base_scope.call.all.model
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def inherited(subclass)
|
|
39
|
+
super
|
|
40
|
+
|
|
41
|
+
subclass.scopes = scopes.dup
|
|
42
|
+
subclass.batch_contexts = batch_contexts.dup
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def scope(name, proc = nil, override: false, &block)
|
|
46
|
+
proc = proc&.to_proc || block
|
|
47
|
+
raise ArgumentError, 'proc or block required' unless proc
|
|
48
|
+
raise ArgumentError, "scope `#{name}' already defined" if !override && scopes.key?(name.to_sym)
|
|
49
|
+
|
|
50
|
+
scopes[name.to_sym] = proc
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def batch_context(name, proc = nil, override: false, &block)
|
|
54
|
+
proc = proc&.to_proc || block
|
|
55
|
+
raise ArgumentError, 'proc or block required' unless proc
|
|
56
|
+
raise ArgumentError, "batch_context `#{name}' already defined" if !override && batch_contexts.key?(name.to_sym)
|
|
57
|
+
|
|
58
|
+
batch_contexts[name.to_sym] = proc
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
attr_reader :start, :finish, :batch_size, :params
|
|
63
|
+
|
|
64
|
+
# @param [Integer] start Specifies the primary key value to start from, inclusive of the value.
|
|
65
|
+
# @param [Integer] finish Specifies the primary key value to end at, inclusive of the value.
|
|
66
|
+
# @param [Integer] batch_size The number of records to be returned in each batch. Defaults to 1000.
|
|
67
|
+
# @param [Hash] params The query criteria
|
|
68
|
+
# @return [Esse::ActiveRecord::Collection]
|
|
69
|
+
def initialize(start: nil, finish: nil, batch_size: nil, **params)
|
|
70
|
+
@start = start
|
|
71
|
+
@finish = finish
|
|
72
|
+
@batch_size = batch_size || self.class.batch_size || 1000
|
|
73
|
+
@params = params
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def each
|
|
77
|
+
dataset.find_in_batches(**batch_options) do |rows|
|
|
78
|
+
kwargs = params.dup
|
|
79
|
+
self.class.batch_contexts.each do |name, proc|
|
|
80
|
+
kwargs[name] = proc.call(rows, **params)
|
|
81
|
+
end
|
|
82
|
+
yield(rows, **kwargs)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def dataset(**kwargs)
|
|
87
|
+
query = self.class.base_scope&.call || raise(NotImplementedError, "No scope defined for #{self.class}")
|
|
88
|
+
query = query.except(:order, :limit, :offset)
|
|
89
|
+
params.merge(kwargs).each do |key, value|
|
|
90
|
+
if self.class.scopes.key?(key)
|
|
91
|
+
scope_proc = self.class.scopes[key]
|
|
92
|
+
query = if scope_proc.arity == 0
|
|
93
|
+
query.instance_exec(&scope_proc)
|
|
94
|
+
else
|
|
95
|
+
query.instance_exec(value, &scope_proc)
|
|
96
|
+
end
|
|
97
|
+
elsif query.model.columns_hash.key?(key.to_s)
|
|
98
|
+
query = query.where(key => value)
|
|
99
|
+
else
|
|
100
|
+
raise ArgumentError, "Unknown scope `#{key}'"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
query
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def inspect
|
|
108
|
+
return super unless self.class < Esse::ActiveRecord::Collection
|
|
109
|
+
return super unless self.class.base_scope
|
|
110
|
+
|
|
111
|
+
vars = instance_variables.map do |n|
|
|
112
|
+
"#{n}=#{instance_variable_get(n).inspect}"
|
|
113
|
+
end
|
|
114
|
+
format('#<Esse::ActiveRecord::Collection__%s:0x%x %s>', self.class.model, object_id, vars.join(', '))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
protected
|
|
118
|
+
|
|
119
|
+
def batch_options
|
|
120
|
+
{
|
|
121
|
+
batch_size: batch_size
|
|
122
|
+
}.tap do |hash|
|
|
123
|
+
hash[:start] = start if start
|
|
124
|
+
hash[:finish] = finish if finish
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Esse
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Hooks
|
|
6
|
+
STORE_STATE_KEY = :esse_active_record_hooks
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def register_model(model_class)
|
|
10
|
+
@models ||= []
|
|
11
|
+
@models |= [model_class]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def models
|
|
15
|
+
@models || []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def model_names
|
|
19
|
+
models.map(&:to_s)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Global enable indexing callbacks. If no repository is specified, all repositories will be enabled.
|
|
23
|
+
# @param repos [Array<String, Esse::Index, Esse::Repo>]
|
|
24
|
+
# @return [void]
|
|
25
|
+
def enable!(*repos)
|
|
26
|
+
filter_repositories(*repos).each do |repo|
|
|
27
|
+
state[:repos][repo] = true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Global disable indexing callbacks. If no repository is specified, all repositories will be disabled.
|
|
32
|
+
# @param repos [Array<String, Esse::Index, Esse::Repo>]
|
|
33
|
+
# @return [void]
|
|
34
|
+
def disable!(*repos)
|
|
35
|
+
filter_repositories(*repos).each do |repo|
|
|
36
|
+
state[:repos][repo] = false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if the given repository is enabled for indexing. If no repository is specified, all repositories will be checked.
|
|
41
|
+
#
|
|
42
|
+
# @param repos [Array<String, Esse::Index, Esse::Repo>]
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
def disabled?(*repos)
|
|
45
|
+
filter_repositories(*repos).all? { |repo| !state[:repos][repo] }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Check if the given repository is enabled for indexing. If no repository is specified, all repositories will be checked.
|
|
49
|
+
#
|
|
50
|
+
# @param repos [Array<String, Esse::Index, Esse::Repo>]
|
|
51
|
+
# @return [Boolean]
|
|
52
|
+
def enabled?(*repos)
|
|
53
|
+
filter_repositories(*repos).all? { |repo| state[:repos][repo] }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Enable model indexing callbacks for the given model. If no repository is specified, all repositories will be enabled.
|
|
57
|
+
#
|
|
58
|
+
# @param model_class [Class]
|
|
59
|
+
# @param repos [Array<String, Esse::Index, Esse::Repo>]
|
|
60
|
+
# @raise [ArgumentError] if model repository is not registered for the given model
|
|
61
|
+
# @return [void]
|
|
62
|
+
def enable_model!(model_class, *repos)
|
|
63
|
+
ensure_registered_model_class!(model_class)
|
|
64
|
+
filter_model_repositories(model_class, *repos).each do |repo|
|
|
65
|
+
state[:models][model_class][repo] = true
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Disable model indexing callbacks for the given model. If no repository is specified, all repositories will be disabled.
|
|
70
|
+
#
|
|
71
|
+
# @param model_class [Class]
|
|
72
|
+
# @param repos [Array<String, Esse::Index, Esse::Repo>]
|
|
73
|
+
# @raise [ArgumentError] if model repository is not registered for the given model
|
|
74
|
+
# @return [void]
|
|
75
|
+
def disable_model!(model_class, *repos)
|
|
76
|
+
ensure_registered_model_class!(model_class)
|
|
77
|
+
filter_model_repositories(model_class, *repos).each do |repo|
|
|
78
|
+
state[:models][model_class][repo] = false
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ensure_registered_model_class!(model_class)
|
|
83
|
+
return if registered_model_class?(model_class)
|
|
84
|
+
|
|
85
|
+
raise ArgumentError, "Model class #{model_class} is not registered. The model should inherit from Esse::ActiveRecord::Model and have a `index_callbacks' callback defined"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check if the given model is enabled for indexing. If no repository is specified, all repositories will be checked.
|
|
89
|
+
#
|
|
90
|
+
# @param model_class [Class]
|
|
91
|
+
# @param repos [Array<String, Esse::Index, Esse::Repo>]
|
|
92
|
+
# @return [Boolean]
|
|
93
|
+
def enabled_for_model?(model_class, *repos)
|
|
94
|
+
return false unless registered_model_class?(model_class)
|
|
95
|
+
|
|
96
|
+
filter_model_repositories(model_class, *repos).all? do |repo|
|
|
97
|
+
state.dig(:models, model_class, repo) != false
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Disable indexing callbacks execution for the block execution.
|
|
102
|
+
# Example:
|
|
103
|
+
# Esse::ActiveRecord::Hooks.without_indexing { User.create! }
|
|
104
|
+
# Esse::ActiveRecord::Hooks.without_indexing(UsersIndex, AccountsIndex.repo(:user)) { User.create! }
|
|
105
|
+
def without_indexing(*repos)
|
|
106
|
+
state_before_disable = state[:repos].dup
|
|
107
|
+
disable!(*repos)
|
|
108
|
+
|
|
109
|
+
yield
|
|
110
|
+
ensure
|
|
111
|
+
state[:repos] = state_before_disable
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Disable model indexing callbacks execution for the block execution for the given model.
|
|
115
|
+
# Example:
|
|
116
|
+
# BroadcastChanges.without_indexing_for_model(User) { }
|
|
117
|
+
# BroadcastChanges.without_indexing_for_model(User, :datasync, :other) { }
|
|
118
|
+
def without_indexing_for_model(model_class, *repos)
|
|
119
|
+
state_before_disable = state[:models].dig(model_class).dup
|
|
120
|
+
disable_model!(model_class, *repos)
|
|
121
|
+
yield
|
|
122
|
+
ensure
|
|
123
|
+
if state_before_disable.nil?
|
|
124
|
+
state[:models].delete(model_class)
|
|
125
|
+
else
|
|
126
|
+
state[:models][model_class] = state_before_disable
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def resolve_index_repository(name)
|
|
131
|
+
index_name, repo_name = name.to_s.underscore.split('::').join('/').split(':', 2)
|
|
132
|
+
if index_name !~ /(I|_i)ndex$/ && index_name !~ /_index\/([\w_]+)$/
|
|
133
|
+
index_name = format('%<index_name>s_index', index_name: index_name)
|
|
134
|
+
end
|
|
135
|
+
klass = index_name.classify.constantize
|
|
136
|
+
return klass if klass <= Esse::Repository
|
|
137
|
+
|
|
138
|
+
repo_name.present? ? klass.repo(repo_name) : klass.repo
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def all_repos
|
|
144
|
+
models.flat_map(&method(:model_repos)).uniq
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Returns a list of all repositories for the given model
|
|
148
|
+
# @return [Array<Symbol>]
|
|
149
|
+
def model_repos(model_class)
|
|
150
|
+
expand_index_repos(*model_class.esse_index_repos.keys)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Returns a list of all repositories for the given model
|
|
154
|
+
# If no repository is specified, all repositories will be returned.
|
|
155
|
+
# @return [Array<*Esse::Repository>] List of repositories
|
|
156
|
+
def filter_repositories(*repos)
|
|
157
|
+
(expand_index_repos(*repos) & all_repos).presence || all_repos
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Return repositorys for the given model. If no repository is specified, all repositories will be returned.
|
|
161
|
+
#
|
|
162
|
+
# @param model_class [Class]
|
|
163
|
+
# @param repos [Array<*Esse::Repository>] List of repositories to check for the given model
|
|
164
|
+
# @return [Array<*Esse::Repository>] List of repositories
|
|
165
|
+
def filter_model_repositories(model_class, *repos)
|
|
166
|
+
model_repos = model_repos(model_class) & all_repos
|
|
167
|
+
(expand_index_repos(*repos) & model_repos).presence || model_repos
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def expand_index_repos(*repos)
|
|
171
|
+
repos.flat_map do |repo_name|
|
|
172
|
+
case repo_name
|
|
173
|
+
when Class
|
|
174
|
+
repo_name <= Esse::Index ? repo_name.repo_hash.values : repo_name
|
|
175
|
+
when String, Symbol
|
|
176
|
+
resolve_index_repository(repo_name)
|
|
177
|
+
else
|
|
178
|
+
raise ArgumentError, "Invalid index or repository name: #{repo_name.inspect}"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Check if model class is registered
|
|
184
|
+
# @return [Boolean] true if model class is registered
|
|
185
|
+
def registered_model_class?(model_class)
|
|
186
|
+
models.include?(model_class)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Data Structure:
|
|
190
|
+
#
|
|
191
|
+
# repos: { <Esse::Repository class> => <true|false>, ... }
|
|
192
|
+
# models: {
|
|
193
|
+
# <ActiveRecord::Base class> => {
|
|
194
|
+
# <Esse::Repository class> => <true|false>
|
|
195
|
+
# }
|
|
196
|
+
# }
|
|
197
|
+
def state
|
|
198
|
+
global_store[STORE_STATE_KEY] ||= {
|
|
199
|
+
repos: all_repos.map { |k| [k, true] }.to_h, # Control global state of the index repository level
|
|
200
|
+
models: Hash.new { |h, k| h[k] = {} }, # Control the state of the model & index repository level
|
|
201
|
+
}
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def global_store
|
|
205
|
+
Thread.current
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Esse
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Model
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
def self.inherited(subclass)
|
|
9
|
+
super
|
|
10
|
+
subclass.esse_index_repos = esse_index_repos.dup
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
attr_reader :esse_index_repos
|
|
15
|
+
|
|
16
|
+
# Define callback for create/update/delete elasticsearch index document after model commit.
|
|
17
|
+
#
|
|
18
|
+
# @param [String] index_repo_name The path of index and repository name.
|
|
19
|
+
# For example a index with a single repository named `users` is `users`. And a index with
|
|
20
|
+
# multiple repositories named `animals` and `dog` as the repository name is `animals/dog`.
|
|
21
|
+
# For namespace, use `/` as the separator.
|
|
22
|
+
# @raise [ArgumentError] when the repo and events are already registered
|
|
23
|
+
# @raise [ArgumentError] when the specified index have multiple repos
|
|
24
|
+
def index_callbacks(index_repo_name, on: %i[create update destroy], **options, &block)
|
|
25
|
+
@esse_index_repos ||= {}
|
|
26
|
+
|
|
27
|
+
operation_name = :index
|
|
28
|
+
if esse_index_repos.dig(index_repo_name, operation_name)
|
|
29
|
+
raise ArgumentError, format('index repository %<name>p already registered %<op>s operation', name: index_repo_name, op: operation_name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
esse_index_repos[index_repo_name] ||= {}
|
|
33
|
+
esse_index_repos[index_repo_name][operation_name] = {
|
|
34
|
+
record: (block || -> { self }),
|
|
35
|
+
options: options,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Esse::ActiveRecord::Hooks.register_model(self)
|
|
39
|
+
|
|
40
|
+
if_enabled = -> { Esse::ActiveRecord::Hooks.enabled?(index_repo_name) && Esse::ActiveRecord::Hooks.enabled_for_model?(self.class, index_repo_name) }
|
|
41
|
+
(on & %i[create update]).each do |event|
|
|
42
|
+
after_commit(on: event, if: if_enabled) do
|
|
43
|
+
opts = self.class.esse_index_repos.fetch(index_repo_name).fetch(operation_name)
|
|
44
|
+
record = opts.fetch(:record)
|
|
45
|
+
record = instance_exec(&record) if record.respond_to?(:call)
|
|
46
|
+
repo = Esse::ActiveRecord::Hooks.resolve_index_repository(index_repo_name)
|
|
47
|
+
document = repo.serialize(record)
|
|
48
|
+
repo.elasticsearch.index_document(document, **opts[:options]) if document
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
(on & %i[destroy]).each do |event|
|
|
53
|
+
after_commit(on: event, if: if_enabled) do
|
|
54
|
+
opts = self.class.esse_index_repos.fetch(index_repo_name).fetch(operation_name)
|
|
55
|
+
record = opts.fetch(:record)
|
|
56
|
+
record = instance_exec(&record) if record.respond_to?(:call)
|
|
57
|
+
repo = Esse::ActiveRecord::Hooks.resolve_index_repository(index_repo_name)
|
|
58
|
+
document = repo.serialize(record)
|
|
59
|
+
repo.elasticsearch.delete_document(document, **opts[:options]) if document
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Disable indexing for the block execution on model level
|
|
66
|
+
# Example:
|
|
67
|
+
# User.without_indexing { }
|
|
68
|
+
# User.without_indexing(UsersIndex, AccountsIndex::User) { }
|
|
69
|
+
def without_indexing(*repos)
|
|
70
|
+
Esse::ActiveRecord::Hooks.without_indexing_for_model(self, *repos) do
|
|
71
|
+
yield
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'esse'
|
|
4
|
+
require 'active_record'
|
|
5
|
+
require_relative 'active_record/version'
|
|
6
|
+
require_relative 'active_record/model'
|
|
7
|
+
require_relative 'active_record/hooks'
|
|
8
|
+
require_relative 'active_record/collection'
|
|
9
|
+
require_relative 'plugins/active_record'
|
|
10
|
+
|
|
11
|
+
module Esse
|
|
12
|
+
module ActiveRecord
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_record'
|
|
4
|
+
|
|
5
|
+
module Esse
|
|
6
|
+
module Plugins
|
|
7
|
+
module ActiveRecord
|
|
8
|
+
module RepositoryClassMethods
|
|
9
|
+
# @param [Class] model_class The ActiveRecord Relation or model class
|
|
10
|
+
# @param [Hash] options The options
|
|
11
|
+
# @option options [Symbol] :batch_size The batch size for the collection
|
|
12
|
+
def collection(*args, **kwargs, &block)
|
|
13
|
+
unless model_or_relation?(args.first)
|
|
14
|
+
return super(*args, **kwargs, &block)
|
|
15
|
+
end
|
|
16
|
+
model_class = args.shift
|
|
17
|
+
|
|
18
|
+
repo = Class.new(Esse::ActiveRecord::Collection)
|
|
19
|
+
repo.base_scope = -> { model_class }
|
|
20
|
+
repo.batch_size = kwargs.delete(:batch_size) if kwargs.key?(:batch_size)
|
|
21
|
+
repo.class_eval(&block) if block
|
|
22
|
+
|
|
23
|
+
super(repo, *args, **kwargs)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def dataset(**params)
|
|
27
|
+
if @collection_proc.nil?
|
|
28
|
+
raise NotImplementedError, "Can't call `dataset' on a repository without a collection defined"
|
|
29
|
+
elsif @collection_proc.is_a?(Class) && @collection_proc < Esse::ActiveRecord::Collection
|
|
30
|
+
@collection_proc.new(**params).dataset
|
|
31
|
+
elsif defined? super
|
|
32
|
+
super
|
|
33
|
+
else
|
|
34
|
+
raise NoMethodError, "undefined method `dataset' for #{self}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def model_or_relation?(klass)
|
|
41
|
+
return true if klass.is_a?(Class) && klass < ::ActiveRecord::Base
|
|
42
|
+
return true if klass.is_a?(::ActiveRecord::Relation)
|
|
43
|
+
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: esse-active_record
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Marcos G. Zimmermann
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-02-14 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: esse
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activerecord
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '4.2'
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '8'
|
|
37
|
+
type: :runtime
|
|
38
|
+
prerelease: false
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '4.2'
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '8'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: awesome_print
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: dotenv
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
type: :development
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: pry
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
type: :development
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
- !ruby/object:Gem::Dependency
|
|
90
|
+
name: rake
|
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '12.3'
|
|
96
|
+
type: :development
|
|
97
|
+
prerelease: false
|
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '12.3'
|
|
103
|
+
- !ruby/object:Gem::Dependency
|
|
104
|
+
name: rspec
|
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '3.0'
|
|
110
|
+
type: :development
|
|
111
|
+
prerelease: false
|
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '3.0'
|
|
117
|
+
- !ruby/object:Gem::Dependency
|
|
118
|
+
name: webmock
|
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '3.14'
|
|
124
|
+
type: :development
|
|
125
|
+
prerelease: false
|
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '3.14'
|
|
131
|
+
- !ruby/object:Gem::Dependency
|
|
132
|
+
name: yard
|
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: 0.9.20
|
|
138
|
+
type: :development
|
|
139
|
+
prerelease: false
|
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: 0.9.20
|
|
145
|
+
- !ruby/object:Gem::Dependency
|
|
146
|
+
name: standard
|
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '1.3'
|
|
152
|
+
type: :development
|
|
153
|
+
prerelease: false
|
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - "~>"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '1.3'
|
|
159
|
+
- !ruby/object:Gem::Dependency
|
|
160
|
+
name: rubocop
|
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - "~>"
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '1.20'
|
|
166
|
+
type: :development
|
|
167
|
+
prerelease: false
|
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - "~>"
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '1.20'
|
|
173
|
+
- !ruby/object:Gem::Dependency
|
|
174
|
+
name: rubocop-performance
|
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
|
176
|
+
requirements:
|
|
177
|
+
- - "~>"
|
|
178
|
+
- !ruby/object:Gem::Version
|
|
179
|
+
version: '1.11'
|
|
180
|
+
- - ">="
|
|
181
|
+
- !ruby/object:Gem::Version
|
|
182
|
+
version: 1.11.5
|
|
183
|
+
type: :development
|
|
184
|
+
prerelease: false
|
|
185
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
186
|
+
requirements:
|
|
187
|
+
- - "~>"
|
|
188
|
+
- !ruby/object:Gem::Version
|
|
189
|
+
version: '1.11'
|
|
190
|
+
- - ">="
|
|
191
|
+
- !ruby/object:Gem::Version
|
|
192
|
+
version: 1.11.5
|
|
193
|
+
- !ruby/object:Gem::Dependency
|
|
194
|
+
name: rubocop-rspec
|
|
195
|
+
requirement: !ruby/object:Gem::Requirement
|
|
196
|
+
requirements:
|
|
197
|
+
- - "~>"
|
|
198
|
+
- !ruby/object:Gem::Version
|
|
199
|
+
version: '2.4'
|
|
200
|
+
type: :development
|
|
201
|
+
prerelease: false
|
|
202
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
203
|
+
requirements:
|
|
204
|
+
- - "~>"
|
|
205
|
+
- !ruby/object:Gem::Version
|
|
206
|
+
version: '2.4'
|
|
207
|
+
description: ActiveRecord extensions for Esse
|
|
208
|
+
email:
|
|
209
|
+
- mgzmaster@gmail.com
|
|
210
|
+
executables: []
|
|
211
|
+
extensions: []
|
|
212
|
+
extra_rdoc_files: []
|
|
213
|
+
files:
|
|
214
|
+
- ".rubocop.yml"
|
|
215
|
+
- Gemfile
|
|
216
|
+
- Gemfile.lock
|
|
217
|
+
- LICENSE.txt
|
|
218
|
+
- README.md
|
|
219
|
+
- Rakefile
|
|
220
|
+
- lib/esse/active_record.rb
|
|
221
|
+
- lib/esse/active_record/collection.rb
|
|
222
|
+
- lib/esse/active_record/hooks.rb
|
|
223
|
+
- lib/esse/active_record/model.rb
|
|
224
|
+
- lib/esse/active_record/version.rb
|
|
225
|
+
- lib/esse/plugins/active_record.rb
|
|
226
|
+
- sig/esse/active_record.rbs
|
|
227
|
+
homepage: https://github.com/marcosgz/esse-active_record
|
|
228
|
+
licenses:
|
|
229
|
+
- MIT
|
|
230
|
+
metadata:
|
|
231
|
+
homepage_uri: https://github.com/marcosgz/esse-active_record
|
|
232
|
+
source_code_uri: https://github.com/marcosgz/esse-active_record
|
|
233
|
+
changelog_uri: https://github.com/marcosgz/esse-active_record/blob/main/CHANGELOG.md
|
|
234
|
+
post_install_message:
|
|
235
|
+
rdoc_options: []
|
|
236
|
+
require_paths:
|
|
237
|
+
- lib
|
|
238
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
239
|
+
requirements:
|
|
240
|
+
- - ">="
|
|
241
|
+
- !ruby/object:Gem::Version
|
|
242
|
+
version: 2.4.0
|
|
243
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
244
|
+
requirements:
|
|
245
|
+
- - ">="
|
|
246
|
+
- !ruby/object:Gem::Version
|
|
247
|
+
version: '0'
|
|
248
|
+
requirements: []
|
|
249
|
+
rubygems_version: 3.0.3.1
|
|
250
|
+
signing_key:
|
|
251
|
+
specification_version: 4
|
|
252
|
+
summary: ActiveRecord extensions for Esse
|
|
253
|
+
test_files: []
|