queryko 1.2.1.beta2 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +71 -13
- data/lib/queryko/able.rb +5 -5
- data/lib/queryko/base.rb +127 -0
- data/lib/queryko/feature.rb +12 -15
- data/lib/queryko/filter_classes.rb +41 -0
- data/lib/queryko/query_object.rb +9 -125
- data/lib/queryko/range_attributes.rb +1 -0
- data/lib/queryko/searchables.rb +1 -1
- data/lib/queryko/version.rb +1 -1
- data/lib/queryko.rb +2 -6
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a59be1cc920578cfddb7246f18fe1435ed9ef7517610bf3ec2f41fe0c5a5556
|
4
|
+
data.tar.gz: 14adefec70cd53df2224961010a3118dd5703aa6d094c150ae54622718d81f6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad6636ad9562467fd758633f97972a885c377b405038f03ed95e9d263e3285ae68dfd07687c22357d2b477187537837a476c9dc0ca303b16e50cef6f53ffff7c
|
7
|
+
data.tar.gz: fa822897bea178f2a853b18e86afb0266c5427b97894895aa926ac1e61fe97b8bac025bf34a08902403e7e9a5c2850c375a59833f2ebb9a7bfdba07534c98474
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
This gem provides additional functionality on your query objects. It will filter and paginate your query by supplying arguments directly from controller params
|
4
4
|
|
5
5
|
## Installation
|
6
|
+
|
6
7
|
```ruby
|
7
8
|
# Pagination
|
8
9
|
gem 'kaminari'
|
@@ -22,33 +23,38 @@ Or install it yourself as:
|
|
22
23
|
$ gem install queryko
|
23
24
|
|
24
25
|
## Usage
|
26
|
+
|
25
27
|
### Create a query object
|
28
|
+
|
26
29
|
``` ruby
|
27
30
|
class ProductsQuery < Queryko::QueryObject
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
super(params, relation)
|
35
|
-
end
|
31
|
+
feature :created_at, :min
|
32
|
+
feature :created_at, :max
|
33
|
+
feature :price, :min
|
34
|
+
feature :price, :max
|
35
|
+
feature :name, :search, as: :name
|
36
|
+
feature :vendor, :search, as: :vendor
|
36
37
|
end
|
37
38
|
```
|
38
39
|
|
39
40
|
### Using your query object
|
40
|
-
Filter your query by appending `_min` or `_max` on your defined attributes. You can also filter
|
41
|
-
|
41
|
+
Filter your query by appending `_min` or `_max` on your defined attributes. You can also filter results by attribute with your defined feature.
|
42
|
+
|
42
43
|
``` ruby
|
43
44
|
# Collection
|
44
45
|
products = ProductsQuery.new(price_min: 100, price_max: 150, name: 'Milk').call
|
45
46
|
products = ProductsQuery.new(since_id: 26).call
|
46
47
|
|
47
|
-
# Count
|
48
|
+
# Count - Counts items on current page. Default page is 1
|
48
49
|
products = ProductsQuery.new(created_at_min: 'Jan 1, 2019').count
|
49
50
|
products = ProductsQuery.new(name: 'Bag').count
|
51
|
+
|
52
|
+
# Total Count - Counts all items matching defined conditions
|
53
|
+
products = ProductsQuery.new(created_at_min: 'Jan 1, 2019').total_count
|
54
|
+
products = ProductsQuery.new(name: 'Bag').total_count
|
50
55
|
```
|
51
56
|
|
57
|
+
|
52
58
|
#### Object Methods
|
53
59
|
- **count** - Returns the filtered count including pagination filter or the size of return object.
|
54
60
|
- **total_count** - Returns the overall count of the filtered total records.
|
@@ -62,19 +68,71 @@ query = ProductsQuery.new(page: 5, limit: 5)
|
|
62
68
|
query.count # 1
|
63
69
|
query.total_count # 21
|
64
70
|
```
|
71
|
+
### Pagination
|
72
|
+
Override these methods to customize pagination limits
|
73
|
+
|
74
|
+
``` ruby
|
75
|
+
...
|
76
|
+
|
77
|
+
def upper_limit
|
78
|
+
20 # default is 100
|
79
|
+
end
|
80
|
+
|
81
|
+
def lower_limit
|
82
|
+
5 # default is 10
|
83
|
+
end
|
84
|
+
|
85
|
+
def default_limit
|
86
|
+
10 # default is 50
|
87
|
+
end
|
88
|
+
|
89
|
+
...
|
90
|
+
```
|
91
|
+
|
92
|
+
### Custom Filters
|
93
|
+
Create a custom filter class using `Queryko::Filters::Base`
|
94
|
+
|
95
|
+
``` ruby
|
96
|
+
class CustomFilters::CoolSearch < Queryko::Filters::Base
|
97
|
+
|
98
|
+
# Optional.
|
99
|
+
# Some `options` keys are reserved for basic functionality
|
100
|
+
# Use `options` to get data from feature definition
|
101
|
+
def intialize(options = {}, feature)
|
102
|
+
super options, feature
|
103
|
+
end
|
104
|
+
|
105
|
+
# Required. This method is called by query object. Always return the result of
|
106
|
+
# the collection
|
107
|
+
def perform(collection, token)
|
108
|
+
collection.where("#{table_name}.#{column_name} < ?", "Cool-#{token}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
Then add the filter class to your Queryko::Base object
|
114
|
+
``` ruby
|
115
|
+
class QueryObject < Queryko::Base
|
116
|
+
filter_class :cool_search, CustomFilters::CoolSearch
|
117
|
+
# or
|
118
|
+
filter_class :cool_search, "CustomFilters::CoolSearch"
|
119
|
+
|
120
|
+
|
121
|
+
feature :name, :cool_search
|
122
|
+
# the :name will be scoped using params[:name_cool_search]
|
123
|
+
end
|
124
|
+
```
|
65
125
|
|
66
126
|
### Other available options
|
67
127
|
| Option | description |
|
68
128
|
|:---------|:-----------------------------|
|
69
129
|
| since_id | retrieves records after `id` |
|
70
130
|
| page | page to retrieve |
|
71
|
-
| limit | number of records per page |
|
72
131
|
|
73
132
|
## Contributing
|
74
133
|
|
75
134
|
Bug reports and pull requests are welcome on GitHub at https://github.com/neume/queryko. 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.
|
76
135
|
|
77
|
-
|
78
136
|
## License
|
79
137
|
|
80
138
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/queryko/able.rb
CHANGED
@@ -3,10 +3,10 @@ module Queryko
|
|
3
3
|
def self.included(base)
|
4
4
|
base.extend(ClassMethods)
|
5
5
|
base.class_eval do
|
6
|
-
class_attribute :
|
6
|
+
class_attribute :defined_filters, default: {}, instance_writer: false
|
7
7
|
class_attribute :features, default: {}, instance_writer: false
|
8
8
|
class_attribute :fields, default: {}, instance_writer: false
|
9
|
-
self.
|
9
|
+
self.defined_filters = {}
|
10
10
|
self.features = {}
|
11
11
|
self.fields = {}
|
12
12
|
|
@@ -18,11 +18,11 @@ module Queryko
|
|
18
18
|
def feature(feature_name, filter, options = {})
|
19
19
|
# returns the feature if it exists
|
20
20
|
feat = self.features[feature_name.to_sym] ||= Queryko::Feature.new feature_name, self
|
21
|
-
self.
|
21
|
+
self.defined_filters[filter] ||= Array.new
|
22
22
|
|
23
23
|
# creates a filter
|
24
|
-
filt = feat.
|
25
|
-
self.
|
24
|
+
filt = feat.add_filter filter, options
|
25
|
+
self.defined_filters[filter].push(filt)
|
26
26
|
|
27
27
|
# appends new field
|
28
28
|
self.fields[filt.field.to_sym] ||= Array.new
|
data/lib/queryko/base.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require "active_support/core_ext/class/attribute"
|
2
|
+
require "queryko/range_attributes"
|
3
|
+
require "queryko/searchables"
|
4
|
+
require "queryko/after_attributes"
|
5
|
+
require "queryko/naming"
|
6
|
+
require "queryko/able"
|
7
|
+
require "queryko/filterer"
|
8
|
+
require "queryko/filter_classes"
|
9
|
+
|
10
|
+
module Queryko
|
11
|
+
class Base
|
12
|
+
attr_reader :countable_resource
|
13
|
+
include Queryko::FilterClasses
|
14
|
+
include Queryko::Naming
|
15
|
+
include Queryko::RangeAttributes
|
16
|
+
include Queryko::Searchables
|
17
|
+
include Queryko::Able
|
18
|
+
include Queryko::Filterer
|
19
|
+
# include AfterAttributes
|
20
|
+
|
21
|
+
def self.inherited(subclass)
|
22
|
+
# It should not be executed when using anonymous class
|
23
|
+
subclass.table_name inferred_from_class_name(subclass) if subclass.name
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(params = {}, rel)
|
27
|
+
@relation = @original_relation = rel || inferred_model.all
|
28
|
+
@params = params
|
29
|
+
end
|
30
|
+
|
31
|
+
def call
|
32
|
+
perform
|
33
|
+
self.relation = paginate if config[:paginate]
|
34
|
+
return relation
|
35
|
+
end
|
36
|
+
|
37
|
+
def perform
|
38
|
+
return if @performed
|
39
|
+
|
40
|
+
@performed = true
|
41
|
+
pre_filter
|
42
|
+
filter
|
43
|
+
filter_by_filters
|
44
|
+
@countable_resource = relation
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def total_count
|
49
|
+
perform
|
50
|
+
countable_resource.count
|
51
|
+
end
|
52
|
+
|
53
|
+
def count
|
54
|
+
call.to_a.count
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_reader :params, :relation
|
60
|
+
attr_writer :relation
|
61
|
+
|
62
|
+
def config
|
63
|
+
@config ||= {
|
64
|
+
paginate: true,
|
65
|
+
since_id: true,
|
66
|
+
ids: true
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def pre_filter
|
71
|
+
self.relation = by_ids if config[:ids] && params[:ids]
|
72
|
+
self.relation = since_id if config[:since_id] && params[:since_id]
|
73
|
+
end
|
74
|
+
|
75
|
+
def filter
|
76
|
+
end
|
77
|
+
|
78
|
+
def paginate
|
79
|
+
if defined?(WillPaginate)
|
80
|
+
relation.paginate(page: page, per_page: limit)
|
81
|
+
elsif defined?(Kaminari)
|
82
|
+
relation.page(page).per(limit)
|
83
|
+
else
|
84
|
+
raise 'Only kaminari and wil_paginate are supported'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def page
|
89
|
+
params[:page] || 1
|
90
|
+
end
|
91
|
+
|
92
|
+
def limit
|
93
|
+
@limit ||= get_limit
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_limit
|
97
|
+
lim = (params[:limit] || default_limit).to_i
|
98
|
+
if lower_limit > lim
|
99
|
+
lower_limit
|
100
|
+
elsif lim > upper_limit
|
101
|
+
upper_limit
|
102
|
+
else
|
103
|
+
lim
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def upper_limit
|
108
|
+
100
|
109
|
+
end
|
110
|
+
|
111
|
+
def default_limit
|
112
|
+
50
|
113
|
+
end
|
114
|
+
|
115
|
+
def lower_limit
|
116
|
+
10
|
117
|
+
end
|
118
|
+
|
119
|
+
def by_ids
|
120
|
+
relation.where(id: params[:ids].split(','))
|
121
|
+
end
|
122
|
+
|
123
|
+
def since_id
|
124
|
+
relation.where("#{defined_table_name}.id > ?", params[:since_id])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/queryko/feature.rb
CHANGED
@@ -1,28 +1,25 @@
|
|
1
1
|
class Queryko::Feature
|
2
|
-
attr_reader :name, :
|
2
|
+
attr_reader :name, :filter_names, :query_object
|
3
3
|
def initialize(name, query_object)
|
4
4
|
@name = name
|
5
5
|
@query_object = query_object
|
6
|
-
@
|
6
|
+
@filter_names = {}
|
7
7
|
end
|
8
8
|
|
9
9
|
def add_filter(filter_name, options = {})
|
10
|
-
self.
|
10
|
+
self.filter_names[filter_name] = create_filter(filter_name, options)
|
11
11
|
end
|
12
12
|
|
13
13
|
def create_filter(filter_name, options = {})
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
result = filters[:before] ||= Queryko::Filters::Before.new(options, self)
|
19
|
-
when :min
|
20
|
-
result = filters[:min] ||= Queryko::Filters::Min.new(options, self)
|
21
|
-
when :max
|
22
|
-
result = filters[:max] ||= Queryko::Filters::Max.new(options, self)
|
23
|
-
when :search
|
24
|
-
result = filters[:search] ||= Queryko::Filters::Search.new(options, self)
|
14
|
+
if filter_class = filter_class_for(filter_name)
|
15
|
+
result = filter_class.new(options, self)
|
16
|
+
else
|
17
|
+
raise "Filter class for #{filter_name} not found"
|
25
18
|
end
|
26
19
|
result
|
27
|
-
|
20
|
+
end
|
21
|
+
|
22
|
+
def filter_class_for(filter_name)
|
23
|
+
self.query_object.filters.fetch(filter_name.to_sym)
|
24
|
+
end
|
28
25
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "queryko/filters/base"
|
2
|
+
require "queryko/filters/after"
|
3
|
+
require "queryko/filters/before"
|
4
|
+
require "queryko/filters/min"
|
5
|
+
require "queryko/filters/max"
|
6
|
+
require "queryko/filters/search"
|
7
|
+
|
8
|
+
module Queryko
|
9
|
+
module FilterClasses
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
base.class_eval do
|
13
|
+
class_attribute :filters, default: {}
|
14
|
+
load_defaults
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def load_defaults
|
20
|
+
self.filters = {
|
21
|
+
after: Queryko::Filters::After,
|
22
|
+
before: Queryko::Filters::Before,
|
23
|
+
min: Queryko::Filters::Min,
|
24
|
+
max: Queryko::Filters::Max,
|
25
|
+
search: Queryko::Filters::Search
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def filter_class(symbol, klass)
|
30
|
+
filters[symbol.to_sym] = constantize_class(klass)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def constantize_class(klass)
|
36
|
+
return klass unless klass.class == String
|
37
|
+
klass.constantize
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/queryko/query_object.rb
CHANGED
@@ -1,125 +1,9 @@
|
|
1
|
-
require "
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
class QueryObject
|
11
|
-
attr_reader :countable_resource
|
12
|
-
include Queryko::Naming
|
13
|
-
include Queryko::RangeAttributes
|
14
|
-
include Queryko::Searchables
|
15
|
-
include Queryko::Able
|
16
|
-
include Queryko::Filterer
|
17
|
-
# include AfterAttributes
|
18
|
-
|
19
|
-
def self.inherited(subclass)
|
20
|
-
# It should not be executed when using anonymous class
|
21
|
-
subclass.table_name inferred_from_class_name(subclass) if subclass.name
|
22
|
-
end
|
23
|
-
|
24
|
-
def initialize(params = {}, rel)
|
25
|
-
@relation = @original_relation = rel || inferred_model.all
|
26
|
-
@params = params
|
27
|
-
end
|
28
|
-
|
29
|
-
def call
|
30
|
-
perform
|
31
|
-
self.relation = paginate if config[:paginate]
|
32
|
-
return relation
|
33
|
-
end
|
34
|
-
|
35
|
-
def perform
|
36
|
-
return if @performed
|
37
|
-
|
38
|
-
@performed = true
|
39
|
-
pre_filter
|
40
|
-
filter
|
41
|
-
filter_by_filters
|
42
|
-
@countable_resource = relation
|
43
|
-
end
|
44
|
-
|
45
|
-
|
46
|
-
def total_count
|
47
|
-
perform
|
48
|
-
countable_resource.count
|
49
|
-
end
|
50
|
-
|
51
|
-
def count
|
52
|
-
call.to_a.count
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
attr_reader :params, :relation
|
58
|
-
attr_writer :relation
|
59
|
-
|
60
|
-
def config
|
61
|
-
@config ||= {
|
62
|
-
paginate: true,
|
63
|
-
since_id: true,
|
64
|
-
ids: true
|
65
|
-
}
|
66
|
-
end
|
67
|
-
|
68
|
-
def pre_filter
|
69
|
-
self.relation = by_ids if config[:ids] && params[:ids]
|
70
|
-
self.relation = since_id if config[:since_id] && params[:since_id]
|
71
|
-
end
|
72
|
-
|
73
|
-
def filter
|
74
|
-
end
|
75
|
-
|
76
|
-
def paginate
|
77
|
-
if defined?(WillPaginate)
|
78
|
-
relation.paginate(page: page, per_page: limit)
|
79
|
-
elsif defined?(Kaminari)
|
80
|
-
relation.page(page).per(limit)
|
81
|
-
else
|
82
|
-
raise 'Only kaminari and wil_paginate are supported'
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def page
|
87
|
-
params[:page] || 1
|
88
|
-
end
|
89
|
-
|
90
|
-
def limit
|
91
|
-
@limit ||= get_limit
|
92
|
-
end
|
93
|
-
|
94
|
-
def get_limit
|
95
|
-
lim = (params[:limit] || default_limit).to_i
|
96
|
-
if lower_limit > lim
|
97
|
-
lower_limit
|
98
|
-
elsif lim > upper_limit
|
99
|
-
upper_limit
|
100
|
-
else
|
101
|
-
lim
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def upper_limit
|
106
|
-
100
|
107
|
-
end
|
108
|
-
|
109
|
-
def default_limit
|
110
|
-
50
|
111
|
-
end
|
112
|
-
|
113
|
-
def lower_limit
|
114
|
-
10
|
115
|
-
end
|
116
|
-
|
117
|
-
def by_ids
|
118
|
-
relation.where(id: params[:ids].split(','))
|
119
|
-
end
|
120
|
-
|
121
|
-
def since_id
|
122
|
-
relation.where("#{defined_table_name}.id > ?", params[:since_id])
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
1
|
+
require "queryko/base"
|
2
|
+
Queryko::QueryObject = Queryko::Base
|
3
|
+
|
4
|
+
message = [
|
5
|
+
"[DEPRECATION] Inheriting from 'Queryko::QueryObject' is depcrecated",
|
6
|
+
"and will be removed on next major release.",
|
7
|
+
"Inherit form 'Queryko::Base' instead."
|
8
|
+
]
|
9
|
+
warn message.join(' ')
|
@@ -11,6 +11,7 @@ module Queryko
|
|
11
11
|
feature arg.to_sym, :min
|
12
12
|
feature arg.to_sym, :max
|
13
13
|
suggestion << "feature :#{arg}, :min"
|
14
|
+
suggestion << "feature :#{arg}, :max"
|
14
15
|
end
|
15
16
|
warn "[DEPRECATION] `add_range_attributes` is deprecated. Please use `feature` instead.\nExample:\n#{suggestion.join("\n")}"
|
16
17
|
end
|
data/lib/queryko/searchables.rb
CHANGED
@@ -9,7 +9,7 @@ module Queryko
|
|
9
9
|
suggestion = []
|
10
10
|
args.each do |arg|
|
11
11
|
feature arg.to_sym, :search, as: arg.to_sym
|
12
|
-
suggestion << "feature :#{arg}, :search, as:
|
12
|
+
suggestion << "feature :#{arg}, :search, as: :#{arg}"
|
13
13
|
end
|
14
14
|
warn "[DEPRECATION] `add_searchables` is deprecated. Please use `feature` instead.\nExample:\n#{suggestion.join("\n")}"
|
15
15
|
end
|
data/lib/queryko/version.rb
CHANGED
data/lib/queryko.rb
CHANGED
@@ -1,14 +1,10 @@
|
|
1
1
|
require "queryko/version"
|
2
2
|
require "queryko/query_object"
|
3
|
-
require "queryko/
|
4
|
-
require "queryko/filters/after"
|
5
|
-
require "queryko/filters/before"
|
6
|
-
require "queryko/filters/min"
|
7
|
-
require "queryko/filters/max"
|
8
|
-
require "queryko/filters/search"
|
3
|
+
require "queryko/base"
|
9
4
|
require "queryko/feature"
|
10
5
|
require "queryko/able"
|
11
6
|
require "queryko/filterer"
|
7
|
+
require "queryko/filter_classes"
|
12
8
|
module Queryko
|
13
9
|
# Your code goes here...
|
14
10
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: queryko
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joseph Nelson Valeros
|
@@ -145,7 +145,9 @@ files:
|
|
145
145
|
- lib/queryko.rb
|
146
146
|
- lib/queryko/able.rb
|
147
147
|
- lib/queryko/after_attributes.rb
|
148
|
+
- lib/queryko/base.rb
|
148
149
|
- lib/queryko/feature.rb
|
150
|
+
- lib/queryko/filter_classes.rb
|
149
151
|
- lib/queryko/filterer.rb
|
150
152
|
- lib/queryko/filters/after.rb
|
151
153
|
- lib/queryko/filters/base.rb
|
@@ -175,9 +177,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
175
177
|
version: '0'
|
176
178
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
177
179
|
requirements:
|
178
|
-
- - "
|
180
|
+
- - ">="
|
179
181
|
- !ruby/object:Gem::Version
|
180
|
-
version:
|
182
|
+
version: '0'
|
181
183
|
requirements: []
|
182
184
|
rubyforge_project:
|
183
185
|
rubygems_version: 2.7.6
|