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