recollect-array 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 536bb7525bf775a16364fae2698019f27c2af8285d6f6731450c84cbb692447a
4
+ data.tar.gz: 1aa573511390953506f5c8b2f677b22d7eacaabb76ec799921dbb03a85b8b588
5
+ SHA512:
6
+ metadata.gz: cffb2f2c9ced4860778c6c3b0bc2854cae9c81e0d6c75a171660586fb9f80780125bfb131c44070ac11b4d8b01b4e391045776196fcd4021c551c324b200c65c
7
+ data.tar.gz: 774943b98fde492185d2500f6fcc92ffa481294c71bcfe190a7885dc4071bedfac12503fc8cbb86f8147e398c9685c20c9876063ba40db5291d5a080f4f5c95d
data/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ [*]
7
+ indent_style = space
8
+ indent_size = 2
9
+ end_of_line = lf
10
+ charset = utf-8
11
+ trim_trailing_whitespace = false
12
+ insert_final_newline = false
@@ -0,0 +1,28 @@
1
+ name: ci
2
+
3
+ on: [push]
4
+
5
+ permissions:
6
+ contents: read
7
+
8
+ jobs:
9
+ rspec:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ ruby-version: ['2.6', '2.7', '3.0', '3.1']
14
+
15
+ steps:
16
+ - uses: actions/checkout@v3
17
+
18
+ - name: Set up Ruby
19
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
20
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
21
+ # uses: ruby/setup-ruby@v1
22
+ uses: ruby/setup-ruby@2b019609e2b0f1ea1a2bc8ca11cb82ab46ada124
23
+ with:
24
+ ruby-version: ${{ matrix.ruby-version }}
25
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
26
+
27
+ - name: Run RSpec
28
+ run: bundle exec rspec --color
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+
14
+ /.idea/
15
+ .byebug_history
16
+ .DS_store
17
+ *.gem
data/.rubocop.yml ADDED
@@ -0,0 +1,65 @@
1
+ Style/Documentation:
2
+ Enabled: false
3
+
4
+ Style/DocumentationMethod:
5
+ Enabled: false
6
+
7
+ Style/ModuleFunction:
8
+ Enabled: false
9
+
10
+ Style/Lambda:
11
+ Enabled: false
12
+
13
+ Metrics/ClassLength:
14
+ Enabled: false
15
+
16
+ Metrics/MethodLength:
17
+ Max: 15
18
+
19
+ Layout/LineLength:
20
+ Max: 150
21
+
22
+ Layout/FirstArrayElementIndentation:
23
+ Enabled: false
24
+
25
+ Layout/MultilineMethodCallIndentation:
26
+ Enabled: false
27
+
28
+ Style/MutableConstant:
29
+ Enabled: true
30
+
31
+ Layout/FirstHashElementIndentation:
32
+ Enabled: false
33
+
34
+ Style/FrozenStringLiteralComment:
35
+ Enabled: true
36
+ SafeAutoCorrect: true
37
+ Exclude:
38
+ - bin/**/*
39
+ - config.ru
40
+ - Gemfile
41
+ - Rakefile
42
+
43
+ AllCops:
44
+ DisabledByDefault: false
45
+ NewCops: enable
46
+ TargetRubyVersion: 3.1
47
+ Exclude:
48
+ - 'spec/factories/*.rb'
49
+ - 'db/schema.rb'
50
+ - *.gemspec
51
+
52
+ Lint/AmbiguousBlockAssociation:
53
+ Exclude:
54
+ - 'spec/**/*.rb'
55
+
56
+ Metrics/BlockLength:
57
+ Exclude:
58
+ - 'spec/**/*.rb'
59
+
60
+ Style/PercentLiteralDelimiters:
61
+ PreferredDelimiters:
62
+ '%i': '()'
63
+
64
+ Lint/DeprecatedConstants:
65
+ Enabled: true
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.2
@@ -0,0 +1,3 @@
1
+ {
2
+ "vscode-run-rspec-file.custom-command": "bundle exec rspec --color"
3
+ }
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at tadeuu@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ ruby '>= 2.5.8'
6
+
7
+ group :test do
8
+ gem 'byebug'
9
+ gem 'rspec'
10
+ gem 'simplecov', require: false
11
+ end
12
+
13
+ gem 'rubocop', require: false
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 Thadeu Esteves Jr
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,477 @@
1
+ <p align="center">
2
+ <h1 align="center">🔃 recollect-array</h1>
3
+ <p align="center"><i>Simple wrapper to filter array using Ruby and simple predicate conditions</i></p>
4
+ </p>
5
+
6
+ <p align="center">
7
+ <a href="https://rubygems.org/gems/recollect-array">
8
+ <img alt="Gem" src="https://img.shields.io/gem/v/recollect-array.svg">
9
+ </a>
10
+
11
+ <a href="https://github.com/thadeu/recollect-array/actions/workflows/ci.yml">
12
+ <img alt="Build Status" src="https://github.com/thadeu/recollect-array/actions/workflows/ci.yml/badge.svg">
13
+ </a>
14
+ </p>
15
+
16
+
17
+ ## Motivation
18
+
19
+ Because in sometimes, we need filter array passing conditions. This gem simplify this work.
20
+
21
+ ## Documentation <!-- omit in toc -->
22
+
23
+ Version | Documentation
24
+ ---------- | -------------
25
+ unreleased | https://github.com/thadeu/recollect-array/blob/main/README.md
26
+
27
+ ## Table of Contents <!-- omit in toc -->
28
+ - [Installation](#installation)
29
+ - [Configuration](#configuration)
30
+ - [Availables Predicates](#availables-predicates)
31
+ - [Usage](#usage)
32
+ - [Utilities](#utilities)
33
+
34
+ ## Compatibility
35
+
36
+ | kind | branch | ruby |
37
+ | -------------- | ------- | ------------------ |
38
+ | unreleased | main | >= 2.5.8, <= 3.1.x |
39
+
40
+ ## Installation
41
+
42
+ Use bundle
43
+
44
+ ```ruby
45
+ bundle add recollect-array
46
+ ```
47
+
48
+ or add this line to your application's Gemfile.
49
+
50
+ ```ruby
51
+ gem 'recollect-array'
52
+ ```
53
+
54
+ ## Configuration
55
+
56
+ Without configuration, because we use only Ruby. ❤️
57
+
58
+ ## Availables Predicates for all values
59
+
60
+ | Type | Suffix | Value |
61
+ | ----------- | ----------- | ----------- |
62
+ | Equal | eq | Anywhere |
63
+ | NotEqual | noteq | Anywhere |
64
+ | Contains | cont | Anywhere |
65
+ | NotContains | notcont | Anywhere |
66
+ | Included | in | Anywhere |
67
+ | NotIncluded | notin | Anywhere |
68
+ | Start | start | Anywhere |
69
+ | NotStart | notstart | Anywhere |
70
+ | End | end | Anywhere |
71
+ | NotEnd | notend | Anywhere |
72
+ | LessThan | lt | Anywhere |
73
+ | LessThanEqual | lteq | Anywhere |
74
+ | GreaterThan | gt | Anywhere |
75
+ | GreaterThanEqual | gteq | Anywhere |
76
+
77
+ ## Availables Predicates only when value is Hash
78
+
79
+ > 💡 Below predicates works only when value is Hash
80
+
81
+ | Type | Suffix | Value |
82
+ | ----------- | ----------- | ----------- |
83
+ | NotEqual | not_eq | Hash |
84
+ | NotContains | not_cont | Hash |
85
+ | NotIncluded | not_in | Hash |
86
+ | NotStart | not_start | Hash |
87
+ | NotEnd | not_end | Hash |
88
+
89
+
90
+ ## Usage
91
+
92
+ <details>
93
+ <summary>Think that your data seems like this.</summary>
94
+
95
+ ```ruby
96
+ data = [
97
+ {
98
+ id: 1,
99
+ name: 'Test #1',
100
+ email: 'test1@email1.com',
101
+ schedule: { all_day: true },
102
+ numbers: %w[1 2],
103
+ active: true,
104
+ count: 9
105
+ },
106
+ {
107
+ id: 2,
108
+ name: 'Test #2',
109
+ email: 'test2@email2.com',
110
+ schedule: { all_day: false },
111
+ numbers: %w[3 4],
112
+ active: true,
113
+ count: 10
114
+ },
115
+ {
116
+ id: 3,
117
+ name: 'Test #3',
118
+ email: 'test3@email3.com',
119
+ schedule: { all_day: false },
120
+ numbers: %w[5 6],
121
+ active: false,
122
+ count: 99
123
+ }
124
+ ]
125
+ ```
126
+ </details>
127
+
128
+ You can use one or multiples predicates in your filter. We see some use cases.
129
+
130
+ ### Flexible Use Case (Hash)
131
+
132
+ **Equal**
133
+
134
+ ```ruby
135
+ filters = {
136
+ active: {
137
+ eq: true
138
+ }
139
+ }
140
+
141
+ collection = Recollect::Array.filter(data, filters)
142
+ ```
143
+
144
+ **NotEqual**
145
+
146
+ ```ruby
147
+ filters = {
148
+ active: {
149
+ # noteq or not_eq
150
+ not_eq: true
151
+ }
152
+ }
153
+
154
+ collection = Recollect::Array.filter(data, filters)
155
+ ```
156
+
157
+ **Nested Hash Paths**
158
+
159
+ ```ruby
160
+ filters = {
161
+ 'schedule.all_day': {
162
+ eq: true
163
+ }
164
+ }
165
+
166
+ collection = Recollect::Array.filter(data, filters)
167
+ ```
168
+
169
+ **Nested Array Paths**
170
+
171
+ > Note the `.0` 🎉
172
+
173
+ ```ruby
174
+ filters = {
175
+ 'numbers.0': {
176
+ eq: '3'
177
+ }
178
+ }
179
+
180
+ collection = Recollect::Array.filter(data, filters)
181
+ ```
182
+
183
+ ```ruby
184
+ filters = {
185
+ numbers: {
186
+ in: '3' # or in: ['3']
187
+ }
188
+ }
189
+
190
+ collection = Recollect::Array.filter(data, filters)
191
+ ```
192
+
193
+ If array, you can navigate into self, using `property.NUMBER.property`
194
+
195
+ ```ruby
196
+ data = [
197
+ {
198
+ schedules: [
199
+ {
200
+ opened: true,
201
+ all_day: true
202
+ },
203
+ {
204
+ opened: false,
205
+ all_day: true
206
+ }
207
+ ]
208
+ },
209
+ {
210
+ schedules: [
211
+ {
212
+ opened: false,
213
+ all_day: true
214
+ },
215
+ {
216
+ opened: false,
217
+ all_day: true
218
+ }
219
+ ]
220
+ }
221
+ ]
222
+
223
+ filters = {
224
+ 'schedules.0.opened': {
225
+ eq: true
226
+ }
227
+ }
228
+
229
+ # OR
230
+
231
+ filters = {
232
+ 'schedules[0]opened': {
233
+ eq: true
234
+ }
235
+ }
236
+
237
+ # OR
238
+
239
+ filters = {
240
+ 'schedules.[0].opened': {
241
+ eq: true
242
+ }
243
+ }
244
+
245
+ collection = Recollect::Array.filter(data, filters)
246
+
247
+ # [{ schedules: [{ opened: true, all_day: true }, { opened: false, all_day: true }] }]
248
+ ```
249
+
250
+ Amazing, you can pass a Callable value as value, like this.
251
+
252
+ ```ruby
253
+ filters = {
254
+ 'schedules.[0].opened': {
255
+ eq: -> { true }
256
+ }
257
+ }
258
+
259
+ # OR a Module
260
+
261
+ module ActiveTruthy
262
+ def self.call = true
263
+ end
264
+
265
+ module NumbersAvailable
266
+ def self.call = %w[1 2]
267
+ end
268
+
269
+ filters = {
270
+ 'schedules.[0].opened': {
271
+ eq: ActiveTruthy
272
+ },
273
+ numbers: {
274
+ in: NumbersAvailable
275
+ }
276
+ }
277
+
278
+ # OR a Class
279
+
280
+ class ActiveFalsey
281
+ def self.call = false
282
+ end
283
+
284
+ filters = {
285
+ 'schedules.[0].opened': {
286
+ eq: ActiveFalsey
287
+ }
288
+ }
289
+
290
+ collection = Recollect::Array.filter(data, filters)
291
+ ```
292
+
293
+ **Combine conditions**
294
+
295
+ Yes, you can combine one or multiple predicates to filter you array.
296
+
297
+
298
+ ```ruby
299
+ filters = {
300
+ active: { eq: true },
301
+ numbers: {
302
+ in: %w[5],
303
+ not_in: '10'
304
+ },
305
+ email: {
306
+ cont: 'email1',
307
+ not_cont: '@gmail'
308
+ },
309
+ 'schedule.all_day': {
310
+ in: [true, false]
311
+ }
312
+ }
313
+
314
+ collection = Recollect::Array.filter(data, filters)
315
+ ```
316
+
317
+ ### Querystring Use Case
318
+
319
+ **Equal**
320
+
321
+ ```ruby
322
+ filters = { active_eq: true }
323
+
324
+ collection = Recollect::Array.filter(data, filters)
325
+ ```
326
+
327
+ **NotEqual**
328
+
329
+ ```ruby
330
+ filters = { active_noteq: true }
331
+
332
+ collection = Recollect::Array.filter(data, filters)
333
+ ```
334
+
335
+ **Nested Hash Paths**
336
+
337
+ ```ruby
338
+ filters = { 'schedule.all_day_eq': false }
339
+
340
+ collection = Recollect::Array.filter(data, filters)
341
+ ```
342
+
343
+ **Nested Array Paths**
344
+
345
+ > Note the `.0` 🎉
346
+
347
+ ```ruby
348
+ filters = { 'numbers.0_eq': '3' }
349
+
350
+ collection = Recollect::Array.filter(data, filters)
351
+ ```
352
+
353
+ ```ruby
354
+ filters = { numbers_in: ['1'] }
355
+
356
+ collection = Recollect::Array.filter(data, filters)
357
+
358
+ expect(collection.result.size).to eq(1)
359
+ ```
360
+
361
+ **Combine conditions**
362
+
363
+ Yes, you can combine one or multiple predicates to filter you array.
364
+
365
+
366
+ ```ruby
367
+ filters = {
368
+ active_noteq: true,
369
+ numbers_in: %w[5],
370
+ email_cont: 'test3',
371
+ 'schedule.all_day_eq': false
372
+ }
373
+
374
+ collection = Recollect::Array.filter(data, filters)
375
+ ```
376
+
377
+ **Receive querystring in your route**
378
+
379
+ Like Ransack, imagine that you receive an querystring and you want to filter your Array. So, you can to do something like this.
380
+
381
+ > ⚠️ But security is your responsability, ok? Let's go!
382
+
383
+ ```ruby
384
+ # receive querystring in your route
385
+ querystring = "active_noteq=true&numbers_in=5&email_cont=test3&schedule.all_day_eq=false"
386
+
387
+ # parse querystring and transform to Hash
388
+ params = URI.decode_www_form(querystring).to_h
389
+
390
+ # filter your collection using params directly.
391
+ collection = Recollect::Array.filter(data, filters)
392
+
393
+ # Beautiful, right? 🎉
394
+ ```
395
+
396
+ ## Utilities
397
+
398
+ ### Recollect::Hashie.get(hash, path)
399
+
400
+ ```ruby
401
+ user = {
402
+ id: 1,
403
+ name: 'Test #1',
404
+ email: 'test1@email1.com',
405
+ schedule: { all_day: true },
406
+ numbers: %w[1 2],
407
+ active: true,
408
+ count: 9
409
+ }
410
+
411
+ result = Recollect::Hashie.get(user, 'id')
412
+ -> 1
413
+
414
+ result = Recollect::Hashie.get(user, 'schedule.all_day')
415
+ -> true
416
+
417
+ result = Recollect::Hashie.get(user, 'numbers')
418
+ -> ['1', '2']
419
+
420
+ result = Recollect::Hashie.get(user, 'numbers.0')
421
+ -> 1
422
+
423
+ result = Recollect::Hashie.get(user, 'numbers[0]')
424
+ -> 1
425
+
426
+ result = Recollect::Hashie.get(user, 'numbers[0][1]')
427
+
428
+ result = Recollect::Hashie.get(user, 'numbers.[0].[1]')
429
+ ```
430
+
431
+ ### Recollect::Array.pluck(array, path)
432
+
433
+ ```ruby
434
+ users = [
435
+ {
436
+ id: 1,
437
+ name: 'Test #1',
438
+ email: 'test1@email1.com',
439
+ schedule: { all_day: true },
440
+ numbers: %w[1 2],
441
+ active: true,
442
+ count: 9
443
+ },
444
+ {
445
+ id: 2,
446
+ name: 'Test #1',
447
+ email: 'test1@email1.com',
448
+ schedule: { all_day: true },
449
+ numbers: %w[1 2],
450
+ active: true,
451
+ count: 9
452
+ }
453
+ ]
454
+
455
+ result = Recollect::Array.pluck(users, 'id')
456
+ $ -> [1, 2]
457
+
458
+ result = Recollect::Array.pluck(users, 'schedule.all_day')
459
+ $ -> [true, true]
460
+ ```
461
+
462
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
463
+
464
+ ## Development
465
+
466
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
467
+
468
+ To install this gem onto your local machine, run `bundle 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
469
+
470
+ ## Contributing
471
+
472
+ Bug reports and pull requests are welcome on GitHub at https://github.com/thadeu/recollect-array. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/thadeu/recollect-array/blob/master/CODE_OF_CONDUCT.md).
473
+
474
+
475
+ ## License
476
+
477
+ 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ RuboCop::RakeTask.new(:rubocop) do |t|
10
+ t.options = ['--display-cop-names']
11
+ end
12
+
13
+ task default: %i(spec rubocop)
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'recollect'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+ IFS=$'\n\t'
5
+ set -vx
6
+
7
+ bundle install
8
+
9
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect
4
+ module Array
5
+ class Filterable
6
+ def self.call(data, filters)
7
+ instance = new(data, filters)
8
+ instance.call!
9
+ instance
10
+ end
11
+
12
+ # Available filter
13
+ attr_accessor :filters
14
+
15
+ attr_reader :result
16
+
17
+ def initialize(data, filters)
18
+ @result = Array(data)
19
+ @filters = filters
20
+ end
21
+ private_class_method :new
22
+
23
+ def call!
24
+ @filters.each(&__apply_filter_callback)
25
+ end
26
+
27
+ def __apply_filter_callback
28
+ lambda do |target|
29
+ key, value = target
30
+
31
+ case value
32
+ when ::Hash
33
+ value.each do |predicate, item_value|
34
+ klass = Predicate.call(predicate)
35
+
36
+ @result.filter! do |item|
37
+ case item_value
38
+ when Proc, Module
39
+ klass.check!(item, key, item_value.call)
40
+ else
41
+ klass.check!(item, key, item_value)
42
+ end
43
+ end
44
+ end
45
+ else
46
+ parts = key.to_s.split('_')
47
+ predicate = parts.pop
48
+ iteratee = parts.join('_')
49
+ klass = Predicate.call(predicate)
50
+
51
+ next unless !!klass
52
+
53
+ @result.filter! do |item|
54
+ klass.check!(item, iteratee, value)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ private :__apply_filter_callback
60
+ end
61
+
62
+ Predicate = lambda do |named|
63
+ {
64
+ eq: Equal, noteq: NotEqual, not_eq: NotEqual,
65
+ cont: Contains, notcont: NotContains, not_cont: NotContains,
66
+ lt: LessThan, lteq: LessThanEqual,
67
+ gt: GreaterThan, gteq: GreaterThanEqual,
68
+ start: Startify, notstart: NotStartify, not_start: NotStartify,
69
+ end: Endify, notend: NotEndify, not_end: NotEndify,
70
+ in: Included, notin: NotIncluded, not_in: NotIncluded
71
+ }[named.to_sym]
72
+ end
73
+ private_constant :Predicate
74
+ end
75
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect::Array
4
+ module Hashie
5
+ # ### Hash.get
6
+ # `fetch value into hash, like Lodash.get`
7
+ #
8
+ # ````
9
+ # hash = { a: 1, b: { c: 2 }, d: ['1'] }
10
+ # Recollect::Hash.get(hash, :b, :c)
11
+ # ````
12
+ #
13
+ # ````
14
+ # hash = { a: 1, b: { c: 2 }, d: ['1'] }
15
+ # Recollect::Hash.get(hash, 'd.0')
16
+ # ````
17
+ #
18
+ # ````
19
+ # hash = { a: 1, b: { c: 2 }, d: [{ e: 3 }] }
20
+ # Recollect::Hash.get(hash, 'd.0.e')
21
+ # ````
22
+ def self.get(data, *keys)
23
+ Utility::TryFetchOrBlank.call(data, *keys)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect::Array
4
+ module Contains
5
+ def self.check!(item, iteratee, value)
6
+ fetched_value = Utility::TryFetchOrBlank[item, iteratee]
7
+ return false unless fetched_value
8
+
9
+ Array(value).any? { |v| fetched_value.to_s.match(/#{v}/) }
10
+ end
11
+ end
12
+
13
+ module NotContains
14
+ def self.check!(item, iteratee, value)
15
+ !Contains.check!(item, iteratee, value)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect::Array
4
+ module Endify
5
+ def self.check!(item, iteratee, value)
6
+ fetched_value = Utility::TryFetchOrBlank[item, iteratee]
7
+ return false unless fetched_value
8
+
9
+ regex = /#{value}$/
10
+ fetched_value.to_s.match?(regex)
11
+ end
12
+ end
13
+
14
+ module NotEndify
15
+ def self.check!(item, iteratee, value)
16
+ !Endify.check!(item, iteratee, value)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect::Array
4
+ module Equal
5
+ def self.check!(item, iteratee, expected_value)
6
+ expected_value == Utility::TryFetchOrBlank[item, iteratee]
7
+ end
8
+ end
9
+
10
+ module NotEqual
11
+ def self.check!(item, iteratee, value)
12
+ !Equal.check!(item, iteratee, value)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect::Array
4
+ module GreaterThan
5
+ def self.check!(item, iteratee, value)
6
+ fetched_value = Utility::TryFetchOrBlank[item, iteratee]
7
+ return false unless fetched_value
8
+
9
+ fetched_value > value
10
+ end
11
+ end
12
+
13
+ module GreaterThanEqual
14
+ def self.check!(item, iteratee, value)
15
+ fetched_value = Utility::TryFetchOrBlank[item, iteratee]
16
+ return false unless fetched_value
17
+
18
+ fetched_value >= value
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect::Array
4
+ module Included
5
+ def self.check!(item, iteratee, value)
6
+ fetched_value = Array(Utility::TryFetchOrBlank[item, iteratee]).compact
7
+ return false unless Array(fetched_value).count > 0
8
+
9
+ fetched_value.any? do |expected_value|
10
+ Array(value).include?(expected_value)
11
+ end
12
+ end
13
+ end
14
+
15
+ module NotIncluded
16
+ def self.check!(item, iteratee, value)
17
+ !Included.check!(item, iteratee, value)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect::Array
4
+ module LessThan
5
+ def self.check!(item, iteratee, value)
6
+ fetched_value = Utility::TryFetchOrBlank[item, iteratee]
7
+ return false unless fetched_value
8
+
9
+ fetched_value < value
10
+ end
11
+ end
12
+
13
+ module LessThanEqual
14
+ def self.check!(item, iteratee, value)
15
+ fetched_value = Utility::TryFetchOrBlank[item, iteratee]
16
+ return false unless fetched_value
17
+
18
+ fetched_value <= value
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect::Array
4
+ module Startify
5
+ def self.check!(item, iteratee, value)
6
+ fetched_value = Utility::TryFetchOrBlank[item, iteratee]
7
+ return false unless fetched_value
8
+
9
+ regex = /^#{value}/
10
+ fetched_value.to_s.match?(regex)
11
+ end
12
+ end
13
+
14
+ module NotStartify
15
+ def self.check!(item, iteratee, value)
16
+ !Startify.check!(item, iteratee, value)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect::Array
4
+ module Utility
5
+ module Keys
6
+ extend self
7
+
8
+ DELIMITER_REGEX = /\[|\]|\./
9
+
10
+ def to_ary(key)
11
+ return [] if key.empty?
12
+
13
+ key.to_s.split(DELIMITER_REGEX).reject(&:empty?)
14
+ end
15
+ alias [] to_ary
16
+
17
+ def to_dig(key)
18
+ enforce_level = ->(level) do
19
+ if level.is_a?(Integer) || level.match?(/^\d$/)
20
+ level.to_i
21
+ else
22
+ level.to_sym
23
+ end
24
+ end
25
+
26
+ Keys[key].map(&enforce_level)
27
+ end
28
+ end
29
+
30
+ # ### Utility::TryFetchOrBlank
31
+ # `fetch value into hash, like Lodash.get`
32
+ #
33
+ # ````
34
+ # hash = { a: 1, b: { c: 2 }, d: ['1'] }
35
+ # Utility::TryFetchOrBlank.call(hash, :b, :c)
36
+ # ````
37
+ #
38
+ # ````
39
+ # hash = { a: 1, b: { c: 2 }, d: ['1'] }
40
+ # Utility::TryFetchOrBlank[hash, 'd.0']
41
+ # ````
42
+ #
43
+ # ````
44
+ # hash = { a: 1, b: { c: 2 }, d: [{ e: 3 }] }
45
+ # Utility::TryFetchOrBlank.(hash, 'd.0.e')
46
+
47
+ # hash = { a: 1, b: { c: 2 }, d: [{ e: 3 }] }
48
+ # Utility::TryFetchOrBlank.(hash, 'd[0]e')
49
+
50
+ # hash = { a: 1, b: { c: 2 }, d: [{ e: 3 }] }
51
+ # Utility::TryFetchOrBlank.(hash, 'd.[0].e')
52
+ # ````
53
+ TryFetchOrBlank = ->(data, *keys) do
54
+ reducer = ->(memo, key) do
55
+ memo.to_h.dig(*Keys.to_dig(key))
56
+ rescue StandardError
57
+ nil
58
+ end
59
+
60
+ keys.reduce(data, &reducer)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recollect
4
+ module Array
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module Recollect
5
+ module Array
6
+ require_relative 'array/utility'
7
+ require_relative 'array/hashie'
8
+ require_relative 'array/predicate/startify'
9
+ require_relative 'array/predicate/endify'
10
+ require_relative 'array/predicate/equal'
11
+ require_relative 'array/predicate/contains'
12
+ require_relative 'array/predicate/in'
13
+ require_relative 'array/predicate/less_than'
14
+ require_relative 'array/predicate/greater_than'
15
+ require_relative 'array/filterable'
16
+
17
+
18
+ # ### Array.filter
19
+ # `filter value into Array using conditions
20
+ #
21
+ # ````
22
+ # data = [{ a: 1, b: { c: 2 }, d: ['1'] }]
23
+ # filters = { a_eq: 1, 'b.c_eq': 2, d_in: ['1'] }
24
+ # Recollect::Array.filter(data, filters)
25
+ # ````
26
+ def self.filter(data, filters = {})
27
+ Filterable.call(data, filters)
28
+ end
29
+
30
+ # ### Array.pluck
31
+ # `fetch value into Array, like Lodash#pluck`
32
+ #
33
+ # ````
34
+ # data = [{ a: 1, b: { c: 2 }, d: ['1'] }]
35
+ # Recollect::Array.pluck(data, 'b.c')
36
+ # ````
37
+ def self.pluck(data, iteratee)
38
+ return [] unless data.any?
39
+
40
+ data.map { |item| Hashie.get(item, iteratee) }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'recollect/array/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'recollect-array'
10
+ spec.version = Recollect::Array::VERSION
11
+ spec.authors = ['Thadeu Esteves']
12
+ spec.email = ['tadeuu@gmail.com']
13
+ spec.summary = 'Simple wrapper to filter array using Ruby and simple predicate conditions'
14
+ spec.description = 'Filter collections using predicates like Ransack gem.'
15
+ spec.homepage = 'https://github.com/thadeu/recollect-array'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+
22
+ spec.required_ruby_version = '>= 2.5.8'
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency 'base_x', '~> 0.8.1'
26
+ spec.add_dependency 'rbnacl', '~> 7.0'
27
+ spec.add_development_dependency 'bundler', '>= 1.14'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.0'
30
+ spec.add_development_dependency 'rubocop', '~> 0.70'
31
+
32
+ # spec.metadata['rubygems_mfa_required'] = 'true'
33
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: recollect-array
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thadeu Esteves
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-02-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base_x
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rbnacl
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '7.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '7.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.70'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.70'
97
+ description: Filter collections using predicates like Ransack gem.
98
+ email:
99
+ - tadeuu@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".editorconfig"
105
+ - ".github/workflows/ci.yml"
106
+ - ".gitignore"
107
+ - ".rubocop.yml"
108
+ - ".ruby-version"
109
+ - ".vscode/settings.json"
110
+ - CODE_OF_CONDUCT.md
111
+ - Gemfile
112
+ - LICENSE
113
+ - README.md
114
+ - Rakefile
115
+ - bin/console
116
+ - bin/setup
117
+ - lib/recollect/array.rb
118
+ - lib/recollect/array/filterable.rb
119
+ - lib/recollect/array/hashie.rb
120
+ - lib/recollect/array/predicate/contains.rb
121
+ - lib/recollect/array/predicate/endify.rb
122
+ - lib/recollect/array/predicate/equal.rb
123
+ - lib/recollect/array/predicate/greater_than.rb
124
+ - lib/recollect/array/predicate/in.rb
125
+ - lib/recollect/array/predicate/less_than.rb
126
+ - lib/recollect/array/predicate/startify.rb
127
+ - lib/recollect/array/utility.rb
128
+ - lib/recollect/array/version.rb
129
+ - recollect-array.gemspec
130
+ homepage: https://github.com/thadeu/recollect-array
131
+ licenses:
132
+ - MIT
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 2.5.8
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 3.1.4
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Simple wrapper to filter array using Ruby and simple predicate conditions
153
+ test_files: []