esse-active_record 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|