rokaki 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +22 -0
- data/README.md +39 -8
- data/lib/rokaki/filter_model.rb +114 -0
- data/lib/rokaki/filterable.rb +76 -0
- data/lib/rokaki/version.rb +1 -1
- data/lib/rokaki.rb +2 -74
- data/rokaki.gemspec +4 -2
- metadata +35 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 732268408b0eaebe95dc53cfd1cb3788c10d198bb47eddb3f4c75d0ff796d98c
|
4
|
+
data.tar.gz: d698e633dd2d8b48ee086c37e35121c870bc16c40a43070f8c8a5f1db04fc8fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2d9eb53031c10422b3133063792d99f534f4e47ef12fcabe61e52f244d221dae997c437665dada3b7036e017fdd6245f24f54b3507a4ee30dfa0ba976cb8c1e
|
7
|
+
data.tar.gz: e632c271dfc09265b2717e348b7becf7fdf401c1921e34edc6cdae67b52cfc0eeebe481815bd608695236cdfb0008c7a6c662b93c89bb605d8ca10fa8cfa2f37
|
data/Gemfile.lock
CHANGED
@@ -6,7 +6,19 @@ PATH
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
activemodel (6.0.0)
|
10
|
+
activesupport (= 6.0.0)
|
11
|
+
activerecord (6.0.0)
|
12
|
+
activemodel (= 6.0.0)
|
13
|
+
activesupport (= 6.0.0)
|
14
|
+
activesupport (6.0.0)
|
15
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
16
|
+
i18n (>= 0.7, < 2)
|
17
|
+
minitest (~> 5.1)
|
18
|
+
tzinfo (~> 1.1)
|
19
|
+
zeitwerk (~> 2.1, >= 2.1.8)
|
9
20
|
coderay (1.1.2)
|
21
|
+
concurrent-ruby (1.1.5)
|
10
22
|
diff-lcs (1.3)
|
11
23
|
ffi (1.11.1)
|
12
24
|
formatador (0.2.5)
|
@@ -24,12 +36,15 @@ GEM
|
|
24
36
|
guard (~> 2.1)
|
25
37
|
guard-compat (~> 1.1)
|
26
38
|
rspec (>= 2.99.0, < 4.0)
|
39
|
+
i18n (1.6.0)
|
40
|
+
concurrent-ruby (~> 1.0)
|
27
41
|
listen (3.1.5)
|
28
42
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
29
43
|
rb-inotify (~> 0.9, >= 0.9.7)
|
30
44
|
ruby_dep (~> 1.2)
|
31
45
|
lumberjack (1.0.13)
|
32
46
|
method_source (0.9.2)
|
47
|
+
minitest (5.11.3)
|
33
48
|
nenv (0.3.0)
|
34
49
|
notiffany (0.1.3)
|
35
50
|
nenv (~> 0.1)
|
@@ -56,12 +71,18 @@ GEM
|
|
56
71
|
rspec-support (3.8.2)
|
57
72
|
ruby_dep (1.5.0)
|
58
73
|
shellany (0.0.1)
|
74
|
+
sqlite3 (1.4.1)
|
59
75
|
thor (0.20.3)
|
76
|
+
thread_safe (0.3.6)
|
77
|
+
tzinfo (1.2.5)
|
78
|
+
thread_safe (~> 0.1)
|
79
|
+
zeitwerk (2.1.9)
|
60
80
|
|
61
81
|
PLATFORMS
|
62
82
|
ruby
|
63
83
|
|
64
84
|
DEPENDENCIES
|
85
|
+
activerecord
|
65
86
|
bundler (~> 2.0)
|
66
87
|
guard
|
67
88
|
guard-rspec
|
@@ -69,6 +90,7 @@ DEPENDENCIES
|
|
69
90
|
rake (~> 10.0)
|
70
91
|
rokaki!
|
71
92
|
rspec (~> 3.0)
|
93
|
+
sqlite3
|
72
94
|
|
73
95
|
BUNDLED WITH
|
74
96
|
2.0.2
|
data/README.md
CHANGED
@@ -9,17 +9,13 @@ It's a simple gem that just provides you with a basic dsl based on the filter pa
|
|
9
9
|
Add this line to your application's Gemfile:
|
10
10
|
|
11
11
|
```ruby
|
12
|
-
gem 'rokaki'
|
12
|
+
gem 'rokaki', git: 'https://github.com/tevio/rokaki.git'
|
13
13
|
```
|
14
14
|
|
15
15
|
And then execute:
|
16
16
|
|
17
17
|
$ bundle
|
18
18
|
|
19
|
-
Or install it yourself as:
|
20
|
-
|
21
|
-
$ gem install rokaki
|
22
|
-
|
23
19
|
## Usage
|
24
20
|
|
25
21
|
To use the basic DSL include the `Rokaki::Filterable` module
|
@@ -45,7 +41,7 @@ A simple example might be:-
|
|
45
41
|
end
|
46
42
|
```
|
47
43
|
|
48
|
-
This
|
44
|
+
This maps attributes `date`, `author_first_name` and `author_last_name` to a filters object with the structure `{ date: '10-10-10', author: { first_name: 'Shteeve' } }`.
|
49
45
|
|
50
46
|
## Additional options
|
51
47
|
You can specify a `filter_key_prefix` and a `filter_key_infix` to change the structure of the accessors.
|
@@ -53,6 +49,41 @@ You can specify a `filter_key_prefix` and a `filter_key_infix` to change the str
|
|
53
49
|
`filter_key_prefix :__` would result in key accessors like `__author_first_name`
|
54
50
|
`filter_key_infix :__` would result in key accessors like `author__first_name`
|
55
51
|
|
52
|
+
### ActiveRecord
|
53
|
+
Include `Rokaki::FilterModel` in any ActiveRecord model (only AR >= 6.0.0 tested so far) you can generate the filter keys and the actual filter lookup code using the `filters` keyword on a model like so:-
|
54
|
+
|
55
|
+
```
|
56
|
+
# Given the models
|
57
|
+
class Author < ActiveRecord::Base
|
58
|
+
has_many :articles, inverse_of: :author
|
59
|
+
end
|
60
|
+
|
61
|
+
class Article < ActiveRecord::Base
|
62
|
+
belongs_to :author, inverse_of: :articles, required: true
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
class ArticleFilter
|
67
|
+
include FilterModel
|
68
|
+
|
69
|
+
filters :date, :title, author: [:first_name, :last_name]
|
70
|
+
|
71
|
+
attr_accessor :filters
|
72
|
+
|
73
|
+
def initialize(filters:, model: Article)
|
74
|
+
@filters = filters
|
75
|
+
@model = model
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
filter = ArticleFilter.new(filters: params[:filters])
|
80
|
+
|
81
|
+
filtered_results = filter.results
|
82
|
+
|
83
|
+
* note: This will currently return full text matches (I hope to add partial matching with like and ilike soon)
|
84
|
+
```
|
85
|
+
|
86
|
+
|
56
87
|
## Development
|
57
88
|
|
58
89
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -61,7 +92,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
61
92
|
|
62
93
|
## Contributing
|
63
94
|
|
64
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
95
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tevio/rokaki. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
65
96
|
|
66
97
|
## License
|
67
98
|
|
@@ -69,4 +100,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
69
100
|
|
70
101
|
## Code of Conduct
|
71
102
|
|
72
|
-
Everyone interacting in the Rokaki project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
103
|
+
Everyone interacting in the Rokaki project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/tevio/rokaki/blob/master/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Rokaki
|
2
|
+
module FilterModel
|
3
|
+
include Filterable
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
include Filterable::ClassMethods
|
10
|
+
private
|
11
|
+
|
12
|
+
def filters(*filter_keys)
|
13
|
+
define_filter_keys *filter_keys
|
14
|
+
|
15
|
+
@_chain_filters = []
|
16
|
+
filter_keys.each do |filter_key|
|
17
|
+
_chain_filter(filter_key) unless filter_key.is_a? Hash
|
18
|
+
_chain_nested filter_key if filter_key.is_a? Hash
|
19
|
+
end
|
20
|
+
|
21
|
+
define_results
|
22
|
+
end
|
23
|
+
|
24
|
+
def _chain_filter(key)
|
25
|
+
@_chain_filters << "@model = @model.where(#{key.to_s}: #{key.to_s}) if #{key.to_s};"
|
26
|
+
end
|
27
|
+
|
28
|
+
def _build_deep_chain(keys)
|
29
|
+
name = @filter_key_prefix.to_s
|
30
|
+
count = keys.size - 1
|
31
|
+
|
32
|
+
joins = ""
|
33
|
+
where = ""
|
34
|
+
out = ""
|
35
|
+
|
36
|
+
leaf = keys.pop
|
37
|
+
|
38
|
+
keys.each_with_index do |key, i|
|
39
|
+
if keys.length == 1
|
40
|
+
name = "#{key}#{filter_key_infix.to_s}#{leaf}"
|
41
|
+
joins = ":#{key}"
|
42
|
+
|
43
|
+
where = "{ #{key.to_s.pluralize}: { #{leaf}: #{name} } }"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# keys.each_with_index do |key, i|
|
48
|
+
# name += key.to_s
|
49
|
+
# name += filter_key_infix.to_s unless count == i
|
50
|
+
|
51
|
+
# joins += "{ #{key.to_s}: " unless count == i
|
52
|
+
# where += "{ #{key.to_s.pluralize}: " unless count == i
|
53
|
+
# out += " }" unless count == i
|
54
|
+
|
55
|
+
# joins += key.to_s if count == i
|
56
|
+
# where += name if count == i
|
57
|
+
# end
|
58
|
+
|
59
|
+
joins = joins += out
|
60
|
+
where = where += out
|
61
|
+
|
62
|
+
@_chain_filters << "@model = @model.joins(#{joins}).where(#{where}) if #{name};"
|
63
|
+
end
|
64
|
+
|
65
|
+
def _chain_nested(filters_object)
|
66
|
+
filters_object.keys.each do |key|
|
67
|
+
deep_chain([key], filters_object[key])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def associated_table(association)
|
72
|
+
@model.reflect_on_association(association).klass.table_name
|
73
|
+
end
|
74
|
+
|
75
|
+
def filter_model(model)
|
76
|
+
@model = model
|
77
|
+
end
|
78
|
+
|
79
|
+
def like(*args)
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def deep_chain(keys, value)
|
84
|
+
if value.is_a? Hash
|
85
|
+
value.keys.map do |key|
|
86
|
+
_keys = keys.dup << key
|
87
|
+
deep_chain(_keys, value[key])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if value.is_a? Array
|
92
|
+
value.each do |av|
|
93
|
+
_keys = keys.dup << av
|
94
|
+
_build_deep_chain(_keys)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
if value.is_a? Symbol
|
99
|
+
_keys = keys.dup << value
|
100
|
+
_build_deep_chain(_keys)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def define_results
|
105
|
+
results_def = "def results;"
|
106
|
+
@_chain_filters.each do |item|
|
107
|
+
results_def += item
|
108
|
+
end
|
109
|
+
results_def += '@model;end;'
|
110
|
+
class_eval results_def
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Rokaki
|
2
|
+
module Filterable
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
# class methods such as define_filter_keys which comprise the dsl
|
8
|
+
#
|
9
|
+
module ClassMethods
|
10
|
+
private
|
11
|
+
|
12
|
+
def define_filter_keys(*filter_keys)
|
13
|
+
filter_keys.each do |filter_key|
|
14
|
+
_build_filter([filter_key]) unless filter_key.is_a? Hash
|
15
|
+
_nested_key filter_key if filter_key.is_a? Hash
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def filter_key_prefix(prefix)
|
20
|
+
@filter_key_prefix ||= prefix
|
21
|
+
end
|
22
|
+
|
23
|
+
def filter_key_infix(infix = :_)
|
24
|
+
@filter_key_infix ||= infix
|
25
|
+
end
|
26
|
+
|
27
|
+
def _build_filter(keys)
|
28
|
+
name = @filter_key_prefix.to_s
|
29
|
+
filters = "filters"
|
30
|
+
count = keys.size - 1
|
31
|
+
|
32
|
+
keys.each_with_index do |key, i|
|
33
|
+
name += key.to_s
|
34
|
+
name += filter_key_infix.to_s unless count == i
|
35
|
+
|
36
|
+
filters += "[:#{key}]"
|
37
|
+
end
|
38
|
+
|
39
|
+
class_eval "def #{name}; filters.dig(*#{keys}); end;", __FILE__, __LINE__
|
40
|
+
end
|
41
|
+
|
42
|
+
def _nested_key(filters_object)
|
43
|
+
filters_object.keys.each do |key|
|
44
|
+
deep_map([key], filters_object[key])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def deep_map(keys, value)
|
49
|
+
if value.is_a? Hash
|
50
|
+
value.keys.map do |key|
|
51
|
+
_keys = keys.dup << key
|
52
|
+
deep_map(_keys, value[key])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if value.is_a? Array
|
57
|
+
value.each do |av|
|
58
|
+
_keys = keys.dup << av
|
59
|
+
_build_filter(_keys)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if value.is_a? Symbol
|
64
|
+
_keys = keys.dup << value
|
65
|
+
_build_filter(_keys)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def filters
|
72
|
+
raise Error, "Filterable object must implement 'filters' method that returns a hash"
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/lib/rokaki/version.rb
CHANGED
data/lib/rokaki.rb
CHANGED
@@ -1,84 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rokaki/version'
|
4
|
+
require 'rokaki/filterable'
|
5
|
+
require 'rokaki/filter_model'
|
4
6
|
|
5
7
|
module Rokaki
|
6
8
|
class Error < StandardError; end
|
7
9
|
|
8
10
|
# include this module for filters dsl in an object
|
9
11
|
#
|
10
|
-
module Filterable
|
11
|
-
def self.included(base)
|
12
|
-
base.extend(ClassMethods)
|
13
|
-
end
|
14
|
-
|
15
|
-
# class methods such as define_filter_keys which comprise the dsl
|
16
|
-
#
|
17
|
-
module ClassMethods
|
18
|
-
private
|
19
|
-
|
20
|
-
def define_filter_keys(*filter_keys)
|
21
|
-
filter_keys.each do |filter_key|
|
22
|
-
_build_filter([filter_key]) unless filter_key.is_a? Hash
|
23
|
-
_nested_key filter_key if filter_key.is_a? Hash
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def filter_key_prefix(prefix)
|
28
|
-
@filter_key_prefix ||= prefix
|
29
|
-
end
|
30
|
-
|
31
|
-
def filter_key_infix(infix = :_)
|
32
|
-
@filter_key_infix ||= infix
|
33
|
-
end
|
34
|
-
|
35
|
-
def _build_filter(keys)
|
36
|
-
name = @filter_key_prefix.to_s
|
37
|
-
filters = "filters"
|
38
|
-
count = keys.size - 1
|
39
|
-
|
40
|
-
keys.each_with_index do |key, i|
|
41
|
-
name += key.to_s
|
42
|
-
name += filter_key_infix.to_s unless count == i
|
43
|
-
|
44
|
-
filters += "[:#{key}]"
|
45
|
-
end
|
46
|
-
|
47
|
-
class_eval "def #{name}; #{filters}; end;", __FILE__, __LINE__
|
48
|
-
end
|
49
|
-
|
50
|
-
def _nested_key(filters_object)
|
51
|
-
filters_object.keys.each do |key|
|
52
|
-
deep_map([key], filters_object[key])
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def deep_map(keys, value)
|
57
|
-
if value.is_a? Hash
|
58
|
-
value.keys.map do |key|
|
59
|
-
_keys = keys.dup << key
|
60
|
-
deep_map(_keys, value[key])
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
if value.is_a? Array
|
65
|
-
value.each do |av|
|
66
|
-
_keys = keys.dup << av
|
67
|
-
_build_filter(_keys)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
if value.is_a? Symbol
|
72
|
-
_keys = keys.dup << value
|
73
|
-
_build_filter(_keys)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
def filters
|
80
|
-
raise Error, "Filterable object must implement 'filters' method that returns a hash"
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
12
|
end
|
data/rokaki.gemspec
CHANGED
@@ -10,12 +10,12 @@ Gem::Specification.new do |spec|
|
|
10
10
|
|
11
11
|
spec.summary = %q{A web request filtering library}
|
12
12
|
spec.description = %q{A dsl for filtering data in web requests}
|
13
|
-
|
13
|
+
spec.homepage = "https://github.com/tevio/rokaki"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
17
17
|
|
18
|
-
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
19
|
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
20
20
|
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
21
21
|
|
@@ -34,4 +34,6 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency "guard"
|
35
35
|
spec.add_development_dependency "pry"
|
36
36
|
spec.add_development_dependency "guard-rspec"
|
37
|
+
spec.add_development_dependency "activerecord"
|
38
|
+
spec.add_development_dependency "sqlite3"
|
37
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rokaki
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steve Martin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -94,6 +94,34 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activerecord
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
97
125
|
description: A dsl for filtering data in web requests
|
98
126
|
email:
|
99
127
|
- steve@martian.media
|
@@ -114,12 +142,15 @@ files:
|
|
114
142
|
- bin/console
|
115
143
|
- bin/setup
|
116
144
|
- lib/rokaki.rb
|
145
|
+
- lib/rokaki/filter_model.rb
|
146
|
+
- lib/rokaki/filterable.rb
|
117
147
|
- lib/rokaki/version.rb
|
118
148
|
- rokaki.gemspec
|
119
|
-
homepage:
|
149
|
+
homepage: https://github.com/tevio/rokaki
|
120
150
|
licenses:
|
121
151
|
- MIT
|
122
|
-
metadata:
|
152
|
+
metadata:
|
153
|
+
homepage_uri: https://github.com/tevio/rokaki
|
123
154
|
post_install_message:
|
124
155
|
rdoc_options: []
|
125
156
|
require_paths:
|