convenient_grouper 0.1.6
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 +15 -0
- data/.gitignore +10 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +21 -0
- data/README.md +121 -0
- data/Rakefile +1 -0
- data/bin/console +7 -0
- data/bin/setup +5 -0
- data/convenient_grouper.gemspec +26 -0
- data/lib/convenient_grouper.rb +45 -0
- data/lib/convenient_grouper/error.rb +4 -0
- data/lib/convenient_grouper/hash_converter.rb +144 -0
- data/lib/convenient_grouper/regex.rb +20 -0
- data/lib/convenient_grouper/version.rb +3 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OTlmMjVmYzk2YmQwYTk5NTc3NTk4YzM5NWM4NDZmMmY3YjcxZjI4MA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MmYxYjAxOWE0MzM4ODhiMTIwYTJlM2M0MGEyN2JmNjE3ZTQzMTZjZA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NmE5MDBhYjRjYTY5ZjYxZGY5YTU0N2Y2OTJlMGM1NjcwMWU1OTRjYWM5NmQ4
|
10
|
+
Y2Y5NjEwZjk3OThmNWQ3Y2FlYWQ1YTYxNzhlMjQzOWZkY2E0MGUxNzliYWM0
|
11
|
+
OTE0MDZkYzRjZmQ4ODNiYjRhNDAxN2RiZTUxOTcxMjliNzkxMjU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
Y2E1OWJkNThkNzNlM2FkODU2MjAxNmE2YWNjMzI3ZTIxYzVhNGQwYjBhNzA5
|
14
|
+
ZjYyN2Y2MzI1ZjI0NTVhZjIwNjc0MzdiMDg2MjkzNTY1NmVlNzI5MTZlOTFh
|
15
|
+
Mzg2MjI2MzMwZmE2NTJiMzdlYTEzNjc4ZmNmMmFjMzNkMjY4Njc=
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
convenient_grouper
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3
|
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 1.9.3
|
4
|
+
- 2.0.0
|
5
|
+
- 2.2.0
|
6
|
+
env: RAKE_ENV=test RAILS_ENV=test
|
7
|
+
before_install:
|
8
|
+
- 'gem install bundler'
|
9
|
+
script:
|
10
|
+
- 'bundle exec rspec'
|
11
|
+
notifications:
|
12
|
+
email:
|
13
|
+
recipients:
|
14
|
+
- humzashah+travis@gmail.com
|
15
|
+
on_success: never
|
16
|
+
on_failure: always
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 TODO: Write your name
|
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,121 @@
|
|
1
|
+
# ConvenientGrouper
|
2
|
+
|
3
|
+
This gem simplifies grouping records. The primary intention is to make group-aggregation-methods easier.
|
4
|
+
|
5
|
+
To use, add the following to your Gemfile.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'convenient_grouper'
|
9
|
+
```
|
10
|
+
|
11
|
+
After `bundle install`, you can provide the grouping details as a hash with the column as the hash's key.
|
12
|
+
|
13
|
+
### Usage:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
ModelName.group(grouping_hash, options)
|
17
|
+
```
|
18
|
+
|
19
|
+
First argument to method is a grouping hash:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
grouping_hash = {
|
23
|
+
column_name: {
|
24
|
+
group_name_one: a_range_or_array, # group if column value within range/array
|
25
|
+
group_name_two: a_specific_value, # group if column value equals
|
26
|
+
|
27
|
+
# comparison operators: !=, <, <=, >, >=
|
28
|
+
# work best for values whose to_s does not yield spaces
|
29
|
+
group_name_four: "!= #{some_value_one}",
|
30
|
+
group_name_five: "> #{some_value_two}",
|
31
|
+
group_name_six: "<= #{some_value_three}",
|
32
|
+
|
33
|
+
# default group
|
34
|
+
nil => some_string # default value is 'others'
|
35
|
+
}
|
36
|
+
}
|
37
|
+
```
|
38
|
+
|
39
|
+
Second argument is an optional hash:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
options = {
|
43
|
+
restrict: boolean # restrict query to grouped conditions. defaults to false.
|
44
|
+
}
|
45
|
+
```
|
46
|
+
|
47
|
+
If you pass only one non-hash argument, you will be using `ActiveRecord`'s own `group` method.
|
48
|
+
|
49
|
+
### Example 1
|
50
|
+
|
51
|
+
Getting profits for different dates and date-ranges from a `Sale` model:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
Sale.group({
|
55
|
+
date_of_sale: {
|
56
|
+
Jan: Date.new(2015, 1)..Date.new(2015, 1, 31),
|
57
|
+
date: Date.new(2014, 4, 5),
|
58
|
+
dates: [Date.new(2014, 1, 2), Date.new(2014, 2, 1)],
|
59
|
+
unspecified: nil
|
60
|
+
}
|
61
|
+
}).sum(:profit)
|
62
|
+
#=> {"Jan" => 1550, "date" => 50, "dates" => 100, "unspecified" => 25, "others" => 50000}
|
63
|
+
```
|
64
|
+
|
65
|
+
### Example 2
|
66
|
+
|
67
|
+
Counting employees in various age-groups:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
Employee.group({
|
71
|
+
age: {
|
72
|
+
young_adults: 18..25,
|
73
|
+
adults: 25..35,
|
74
|
+
seniors: ">= 60",
|
75
|
+
nil => 'mature_adults'
|
76
|
+
}
|
77
|
+
}).count
|
78
|
+
#=> {"young_adults" => 13, "adults" => 15, "seniors" => 4, "mature_adults" => 5}
|
79
|
+
```
|
80
|
+
|
81
|
+
### Example 3
|
82
|
+
|
83
|
+
Grouping events that are occurring after a particular date:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
date = Date.new(2015, 6, 22)
|
87
|
+
Event.group(date: {
|
88
|
+
not_on_date: ">= #{date}"
|
89
|
+
})
|
90
|
+
```
|
91
|
+
|
92
|
+
### Example 4
|
93
|
+
|
94
|
+
Restricting your query to the conditions specified in your `group` clause:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
range = 13..19
|
98
|
+
Relative.group({
|
99
|
+
age: {teens: range}
|
100
|
+
}, restrict: true) # == Employee.where(age: range).group(hash)
|
101
|
+
```
|
102
|
+
|
103
|
+
### Heads-Up
|
104
|
+
|
105
|
+
A heads-up for specifying the extra options such as `restrict`:
|
106
|
+
|
107
|
+
Ruby will interpret the following as a single hash-argument:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
Relative.group(age: {teens: 13..19}, restrict: true) # wrong
|
111
|
+
```
|
112
|
+
|
113
|
+
Therefore you will need appropriate curly braces:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
Relative.group({age: {teens: 13..19}}, {restrict: true}) # right
|
117
|
+
```
|
118
|
+
|
119
|
+
### Bug Reports
|
120
|
+
|
121
|
+
Things seemingly work well for the most usual use-cases. Nonetheless, bug-reports are welcomed.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'convenient_grouper/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "convenient_grouper"
|
8
|
+
spec.version = ConvenientGrouper::VERSION
|
9
|
+
spec.authors = ["Syed Humza Shah"]
|
10
|
+
spec.email = ["humzashah+github@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "Use Ruby hashes to group database table rows through ActiveRecord."
|
13
|
+
spec.homepage = "https://github.com/humzashah/convenient_grouper"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.required_ruby_version = '>= 1.9.3'
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", ">= 1.8"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "activerecord", ">= 4.0"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "convenient_grouper/hash_converter"
|
3
|
+
require "convenient_grouper/version"
|
4
|
+
|
5
|
+
module ConvenientGrouper
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def preliminaries(hash_arg, opts)
|
9
|
+
hc = get_hash_converter(hash_arg, opts)
|
10
|
+
|
11
|
+
{
|
12
|
+
groups: get_groups(hc, hash_arg),
|
13
|
+
restrictions: get_restrictions(hc)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
module_function
|
19
|
+
|
20
|
+
def get_hash_converter(hash_arg, opts)
|
21
|
+
return unless hash_arg.is_a?(Hash)
|
22
|
+
ConvenientGrouper::HashConverter.new(hash_arg, opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_groups(hc, hash_arg)
|
26
|
+
hc.try(:groups) || hash_arg
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_restrictions(hc)
|
30
|
+
hc.try(:restrictions) || ""
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
ActiveRecord::Relation.class_eval do
|
35
|
+
define_method :group do |hash_arg, opts={}|
|
36
|
+
if hash_arg.is_a?(Hash)
|
37
|
+
hash = ConvenientGrouper.preliminaries(hash_arg, opts)
|
38
|
+
groups = hash[:groups]
|
39
|
+
restrictions = hash[:restrictions]
|
40
|
+
super(groups).where(restrictions)
|
41
|
+
else
|
42
|
+
super(hash_arg)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require_relative "error"
|
2
|
+
require_relative "regex"
|
3
|
+
|
4
|
+
module ConvenientGrouper
|
5
|
+
class HashConverter
|
6
|
+
attr_reader :groups, :restrictions
|
7
|
+
|
8
|
+
module Default
|
9
|
+
GROUP = 'others'
|
10
|
+
|
11
|
+
OPTIONS = {
|
12
|
+
restrict: false
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def initialize(hash_arg, options=Default::OPTIONS)
|
19
|
+
@hash = hash_arg
|
20
|
+
validate_hash
|
21
|
+
|
22
|
+
@options = options
|
23
|
+
validate_options
|
24
|
+
|
25
|
+
@restrict = (@options[:restrict] == true)
|
26
|
+
|
27
|
+
create_groups
|
28
|
+
create_restrictions
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate_hash
|
32
|
+
raise_error("Incompatible hash: #{@hash}.") unless matches_conditions?
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_options
|
36
|
+
valid_keys = Default::OPTIONS.keys
|
37
|
+
invalid_keys = @options.keys - valid_keys
|
38
|
+
return if valid = invalid_keys.empty?
|
39
|
+
|
40
|
+
valid_keys_str = valid_keys.join(', ')
|
41
|
+
invalid_keys_str = invalid_keys.join(', ')
|
42
|
+
raise_error "You provided invalid options: #{invalid_keys_str}. Supported options include: #{valid_keys_str}."
|
43
|
+
end
|
44
|
+
|
45
|
+
def matches_conditions?
|
46
|
+
@hash.is_a?(Hash) &&
|
47
|
+
(@hash.each_key.count == 1) &&
|
48
|
+
@hash.each_value.all? { |v| v.is_a?(Hash) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_groups
|
52
|
+
@groups = "CASE #{[*cases, default_group].compact.join(' ')} END"
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_restrictions
|
56
|
+
@restrictions =
|
57
|
+
if @restrict
|
58
|
+
parse_hash do |column, _, value|
|
59
|
+
column_value_matcher(column, value)
|
60
|
+
end.join(' OR ')
|
61
|
+
end || ""
|
62
|
+
end
|
63
|
+
|
64
|
+
def cases
|
65
|
+
parse_hash do |column, group, value|
|
66
|
+
"WHEN #{column_value_matcher(column, value)} THEN '#{group}'"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_hash
|
71
|
+
@hash.each_with_object([]) do |(column, values_hash), array|
|
72
|
+
values_hash.each do |group, value|
|
73
|
+
array << yield(column, group, value) if group
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def column_value_matcher(column, value)
|
79
|
+
"(#{column} #{lookup_str(value)})"
|
80
|
+
end
|
81
|
+
|
82
|
+
def default_group
|
83
|
+
hash = @hash.values.first
|
84
|
+
group = hash[nil] || Default::GROUP
|
85
|
+
"ELSE '#{group}'"
|
86
|
+
end
|
87
|
+
|
88
|
+
def lookup_str(value)
|
89
|
+
case value
|
90
|
+
when Range
|
91
|
+
range_str(value)
|
92
|
+
when Array
|
93
|
+
array_str(value)
|
94
|
+
when lambda { |x| Regex.matches?(x) }
|
95
|
+
value
|
96
|
+
when Numeric, String
|
97
|
+
value_str(value)
|
98
|
+
when NilClass
|
99
|
+
'IS NULL'
|
100
|
+
else
|
101
|
+
raise_error("Unsupported type #{value.class.name} for #{value}.")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def range_str(range)
|
106
|
+
validate_range(range)
|
107
|
+
|
108
|
+
min = insert_val(range.min)
|
109
|
+
max = insert_val(range.max)
|
110
|
+
|
111
|
+
"BETWEEN #{min} AND #{max}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_range(range)
|
115
|
+
min = range.min
|
116
|
+
max = range.max
|
117
|
+
|
118
|
+
valid = min && max && (min < max)
|
119
|
+
raise_error("Improper range: #{range}.") unless valid
|
120
|
+
end
|
121
|
+
|
122
|
+
def array_str(array)
|
123
|
+
mapped_str = array.map { |v| insert_val(v) }.join(', ')
|
124
|
+
"IN (#{mapped_str})"
|
125
|
+
end
|
126
|
+
|
127
|
+
def value_str(value)
|
128
|
+
"= #{insert_val(value)}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def insert_val(value)
|
132
|
+
if value.is_a?(Numeric)
|
133
|
+
value
|
134
|
+
else
|
135
|
+
"'#{value.to_s}'"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def raise_error(msg)
|
140
|
+
raise Error.new(msg)
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ConvenientGrouper
|
2
|
+
module Regex
|
3
|
+
|
4
|
+
module Expression
|
5
|
+
COMPARISON = /^[<, >]={0,1}\s.+$/ # e.g. '> 1' and '<= 4'
|
6
|
+
INEQUALITY = /!=.+/ # e.g. "!= 4" and "!= 'one'"
|
7
|
+
NOT_NULL = /^IS NOT NULL$/i
|
8
|
+
end
|
9
|
+
|
10
|
+
ALL_EXPRESSIONS = Expression.constants.map { |symbol| Expression.const_get(symbol) }
|
11
|
+
|
12
|
+
module_function
|
13
|
+
def matches?(value)
|
14
|
+
value.is_a?(String) &&
|
15
|
+
ALL_EXPRESSIONS.any? do |expression|
|
16
|
+
expression.match(value.strip)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: convenient_grouper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Syed Humza Shah
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- humzashah+github@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .ruby-gemset
|
78
|
+
- .ruby-version
|
79
|
+
- .travis.yml
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/console
|
85
|
+
- bin/setup
|
86
|
+
- convenient_grouper.gemspec
|
87
|
+
- lib/convenient_grouper.rb
|
88
|
+
- lib/convenient_grouper/error.rb
|
89
|
+
- lib/convenient_grouper/hash_converter.rb
|
90
|
+
- lib/convenient_grouper/regex.rb
|
91
|
+
- lib/convenient_grouper/version.rb
|
92
|
+
homepage: https://github.com/humzashah/convenient_grouper
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.9.3
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 2.4.3
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Use Ruby hashes to group database table rows through ActiveRecord.
|
116
|
+
test_files: []
|