active_enumerable 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8b42bdf39bf71038c5874ee160fd48a83a3055d0
4
- data.tar.gz: 84c95fda2a65d555b3b25071bfd14f826ea7f198
3
+ metadata.gz: 016716ccb116b9441ef93f1d0698201c9b41ac50
4
+ data.tar.gz: a164e13d533c2aa282b9d7663ee65f1a28b8ea17
5
5
  SHA512:
6
- metadata.gz: 92a9a0c81dd3dbf728b53df5870cd18ad69a7dcc21f36d177e1f5306d161eeadaf3d896645118df068bfded6b14d3d8b05e498b60047bbc74a816189eca588a3
7
- data.tar.gz: 40f02181da88f8d49d9ab5df82628907f35c2ba8598628dea895f297274bf36dac553ec671d78aa486e1b798336ed85191eeb905bf273d4222f5c8017236a571
6
+ metadata.gz: 7ab10d01e6738b89b9f375b1a1df9156f5cf8c2a3f96b026e698f244c232fd73a9188adc3523d678c721bb995b5902620e0b04dc4647c6fb90148e26f3112cb1
7
+ data.tar.gz: cb3a52fd97b0aae04319d7749e8df84b23fa9b30747cbc54c861f90b9c0d009ee6ba695e6dfb6a4db185fe541999e524ce5d72b86c799afc94a7ebdf4aac4c47
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # ActiveEnumerable
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/active_enumerable`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Include ActiveRecord like query methods to Ruby enumerable collections.
6
4
 
7
5
  ## Installation
8
6
 
@@ -22,7 +20,33 @@ Or install it yourself as:
22
20
 
23
21
  ## Usage
24
22
 
25
- TODO: Write usage instructions here
23
+ ```ruby
24
+ require "active_enumerable"
25
+
26
+ class Customers
27
+ include ActiveEnumerable
28
+
29
+ scope :unpaid, -> { where(paid: false).or(credit: 0) }
30
+ end
31
+
32
+ customers = Customers.new([{paid: true, credit: 1000}, {paid: false, credit: 2000}, {paid: false, credit: 0}])
33
+
34
+ customers.unpaid
35
+ # => <#Customers [{:paid=>false, :credit=>2000}, {:paid=>false, :credit=>0}]]>
36
+
37
+ customers.scope { select { |y| y > 1000 } }
38
+ #=> <#Customers [{paid: true, credit: 1000}, {paid: false, credit: 2000}]>
39
+
40
+ customers.sum(:credit)
41
+ #=> 3000
42
+
43
+ customers.create({paid: true, credit: 1500}) # defaults to Hash creations
44
+
45
+ Customers.item_class = Customer
46
+
47
+ customers.create({paid: true, credit: 1500}).to_a.last
48
+ #=> <#Customer paid: true, credit: 1500>
49
+ ```
26
50
 
27
51
  ## Development
28
52
 
@@ -32,7 +56,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
56
 
33
57
  ## Contributing
34
58
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_enumerable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
59
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zeisler/active_enumerable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
36
60
 
37
61
 
38
62
  ## License
@@ -1,5 +1,25 @@
1
1
  require "active_enumerable/version"
2
+ require "active_enumerable/base"
3
+ require "active_enumerable/comparable"
4
+ require "active_enumerable/enumerable"
5
+ require "active_enumerable/finder"
6
+ require "active_enumerable/method_caller"
7
+ require "active_enumerable/scopes"
8
+ require "active_enumerable/where"
9
+ require "active_enumerable/queries"
2
10
 
3
11
  module ActiveEnumerable
4
- # Your code goes here...
12
+ include Base
13
+ include Enumerable
14
+ include Comparable
15
+ include Queries
16
+ include Scopes
17
+
18
+ module ClassMethods
19
+ include Scopes::ClassMethods
20
+ end
21
+
22
+ def self.included(base)
23
+ base.extend(ClassMethods)
24
+ end
5
25
  end
@@ -0,0 +1,44 @@
1
+ module ActiveEnumerable
2
+ module Base
3
+ def initialize(collection=[])
4
+ @to_a = collection.to_ary
5
+ end
6
+
7
+ attr_reader :to_a
8
+
9
+ # @private
10
+ def __new_relation__(collection)
11
+ self.class.new(collection)
12
+ end
13
+
14
+ def create(attributes)
15
+ add(if (klass = self.class.item_class)
16
+ klass.new(attributes)
17
+ else
18
+ attributes
19
+ end)
20
+ end
21
+
22
+ def add(item)
23
+ to_a << item
24
+ end
25
+
26
+ def all
27
+ self
28
+ end
29
+
30
+ module ClassMethods
31
+ def item_class
32
+ @item_class
33
+ end
34
+
35
+ def item_class=(klass)
36
+ @item_class = klass
37
+ end
38
+ end
39
+
40
+ def self.included(base)
41
+ base.extend(ClassMethods)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveEnumerable
2
+ module Comparable
3
+ include ::Comparable
4
+
5
+ def <=>(anOther)
6
+ if anOther.is_a?(Array) || self.class == anOther.class
7
+ to_a <=> anOther.to_a
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveEnumerable
2
+ module Enumerable
3
+ include ::Enumerable
4
+
5
+ def each(*args, &block)
6
+ @to_a.send(:each, *args, &block)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveEnumerable
2
+ # @private
3
+ class Finder
4
+ def initialize(record)
5
+ @method_caller = MethodCaller.new(record)
6
+ end
7
+
8
+ def is_of(conditions={})
9
+ conditions.all? do |col, match|
10
+ if match.is_a? Hash
11
+ hash_match(col, match)
12
+ elsif match.is_a? ::Enumerable
13
+ any_match(col, match)
14
+ else
15
+ compare(col, match)
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def hash_match(col, match)
23
+ next_record = @method_caller.call(col)
24
+ if next_record.is_a? Array
25
+ next_record.any? { |record| Finder.new(record).is_of(match) }
26
+ else
27
+ Finder.new(next_record).is_of(match)
28
+ end
29
+ end
30
+
31
+ def any_match(col, match)
32
+ match.any? { |m| compare(col, m) }
33
+ end
34
+
35
+ def compare(col, match)
36
+ @method_caller.call(col) == match
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveEnumerable
2
+ # @private
3
+ class MethodCaller
4
+ attr_reader :object, :raise_no_method
5
+
6
+ def initialize(object, raise_no_method: true)
7
+ @object = object
8
+ @raise_no_method = raise_no_method
9
+ end
10
+
11
+ def call(method)
12
+ if object.is_a? Hash
13
+ object.fetch(method)
14
+ else
15
+ object.public_send(method)
16
+ end
17
+ rescue NoMethodError, KeyError => e
18
+ raise e if raise_no_method
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,234 @@
1
+ module ActiveEnumerable
2
+ module Queries
3
+ include ActiveEnumerable::Where
4
+
5
+ # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
6
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
7
+ # is an integer, find by id coerces its arguments using +to_i+.
8
+ #
9
+ # <#ActiveEnumerable>.find(1) # returns the object for ID = 1
10
+ # <#ActiveEnumerable>.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
11
+ # <#ActiveEnumerable>.find([7, 17]) # returns an array for objects with IDs in (7, 17)
12
+ # <#ActiveEnumerable>.find([1]) # returns an array for the object with ID = 1
13
+ #
14
+ # <tt>ActiveEnumerable::RecordNotFound</tt> will be raised if one or more ids are not found.
15
+ def find(ids)
16
+ raise RecordNotFound.new("Couldn't find #{self.name} without an ID") if ids.nil?
17
+ results = [*ids].map do |id|
18
+ find_by!(id: id.to_i)
19
+ end
20
+ return __new_relation__(results) if ids.class == Array
21
+ results.first
22
+ end
23
+
24
+ # Updates all records with details given if they match a set of conditions supplied, limits and order can
25
+ # also be supplied.
26
+ #
27
+ # ==== Parameters
28
+ #
29
+ # * +updates+ - A string, array, or hash.
30
+ #
31
+ # ==== Examples
32
+ #
33
+ # # Update all customers with the given attributes
34
+ # <#ActiveEnumerable>.update_all wants_email: true
35
+ #
36
+ # # Update all books with 'Rails' in their title
37
+ # <#ActiveEnumerable>.where(title: 'Rails').update_all(author: 'David')
38
+ #
39
+ # # Update all books that match conditions, but limit it to 5 ordered by date
40
+ # <#ActiveEnumerable>.where(title: 'Rails').order(:created_at).limit(5).update_all(author: 'David')
41
+ def update_all(attributes)
42
+ all.each { |i| i.update(attributes) }
43
+ end
44
+
45
+ # Updates an object (or multiple objects) and saves it.
46
+ #
47
+ # ==== Parameters
48
+ #
49
+ # * +id+ - This should be the id or an array of ids to be updated.
50
+ # * +attributes+ - This should be a hash of attributes or an array of hashes.
51
+ #
52
+ # ==== Examples
53
+ #
54
+ # # Updates one record
55
+ # <#ActiveEnumerable>.update(15, user_name: 'Samuel', group: 'expert')
56
+ #
57
+ # # Updates multiple records
58
+ # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
59
+ # <#ActiveEnumerable>.update(people.keys, people.values)
60
+ def update(id, attributes)
61
+ if id.is_a?(Array)
62
+ id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
63
+ else
64
+ object = find(id)
65
+ object.update(attributes)
66
+ object
67
+ end
68
+ end
69
+
70
+ # Finds the first record matching the specified conditions. There
71
+ # is no implied ordering so if order matters, you should specify it
72
+ # yourself.
73
+ #
74
+ # If no record is found, returns <tt>nil</tt>.
75
+ #
76
+ # <#ActiveEnumerable>.find_by name: 'Spartacus', rating: 4
77
+ def find_by(conditions = {})
78
+ to_a.detect do |record|
79
+ Finder.new(record).is_of(conditions)
80
+ end
81
+ end
82
+
83
+ # Like <tt>find_by</tt>, except that if no record is found, raises
84
+ # an <tt>ActiveEnumerable::RecordNotFound</tt> error.
85
+ def find_by!(conditions={})
86
+ result = find_by(conditions)
87
+ if result.nil?
88
+ raise RecordNotFound.new("Couldn't find #{self.name} with '#{conditions.keys.first}'=#{conditions.values.first}")
89
+ end
90
+ result
91
+ end
92
+
93
+ # Finds the first record with the given attributes, or creates a record
94
+ # with the attributes if one is not found:
95
+ #
96
+ # # Find the first user named "Penélope" or create a new one.
97
+ # <#ActiveEnumerable>.find_or_create_by(first_name: 'Penélope')
98
+ # # => #<User id: 1, first_name: "Penélope", last_name: nil>
99
+ #
100
+ # # Find the first user named "Penélope" or create a new one.
101
+ # # We already have one so the existing record will be returned.
102
+ # <#ActiveEnumerable>.find_or_create_by(first_name: 'Penélope')
103
+ # # => #<User id: 1, first_name: "Penélope", last_name: nil>
104
+ #
105
+ # This method accepts a block, which is passed down to +create+. The last example
106
+ # above can be alternatively written this way:
107
+ #
108
+ # # Find the first user named "Scarlett" or create a new one with a
109
+ # # different last name.
110
+ # <#ActiveEnumerable>.find_or_create_by(first_name: 'Scarlett') do |user|
111
+ # user.last_name = 'Johansson'
112
+ # end
113
+ # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
114
+ #
115
+ def find_or_create_by(attributes, &block)
116
+ find_by(attributes) || create(attributes, &block)
117
+ end
118
+
119
+ alias_method :find_or_create_by!, :find_or_create_by
120
+
121
+ # Like <tt>find_or_create_by</tt>, but calls <tt>new</tt> instead of <tt>create</tt>.
122
+ def find_or_initialize_by(attributes, &block)
123
+ find_by(attributes) || new(attributes, &block)
124
+ end
125
+
126
+ # Count the records.
127
+ #
128
+ # <#ActiveEnumerable>.count
129
+ # # => the total count of all people
130
+ #
131
+ # <#ActiveEnumerable>.count(:age)
132
+ # # => returns the total count of all people whose age is not nil
133
+ def count(name = nil)
134
+ return all.size if name.nil?
135
+ where.not(name => nil).size
136
+ end
137
+
138
+ # Specifies a limit for the number of records to retrieve.
139
+ #
140
+ # <#ActiveEnumerable>.limit(10)
141
+ def limit(num)
142
+ relation = __new_relation__(all.take(num))
143
+ relation.send(:set_from_limit)
144
+ relation
145
+ end
146
+
147
+ # Calculates the sum of values on a given attribute. The value is returned
148
+ # with the same data type of the attribute, 0 if there's no row.
149
+ #
150
+ # <#ActiveEnumerable>.sum(:age) # => 4562
151
+ def sum(key)
152
+ values = values_by_key(key)
153
+ values.inject(0) do |sum, n|
154
+ sum + (n || 0)
155
+ end
156
+ end
157
+
158
+ # Calculates the average value on a given attribute. Returns +nil+ if there's
159
+ # no row.
160
+ #
161
+ # <#ActiveEnumerable>.average(:age) # => 35.8
162
+ def average(key)
163
+ values = values_by_key(key)
164
+ total = values.inject { |sum, n| sum + n }
165
+ BigDecimal.new(total) / BigDecimal.new(values.count)
166
+ end
167
+
168
+ # Calculates the minimum value on a given attribute. The value is returned
169
+ # with the same data type of the attribute, or +nil+ if there's no row.
170
+ #
171
+ # <#ActiveEnumerable>.minimum(:age) # => 7
172
+ def minimum(key)
173
+ values_by_key(key).min_by { |i| i }
174
+ end
175
+
176
+ # Calculates the maximum value on a given attribute. The value is returned
177
+ # with the same data type of the attribute, or +nil+ if there's no row.
178
+ #
179
+ # <#ActiveEnumerable>.maximum(:age) # => 93
180
+ def maximum(key)
181
+ values_by_key(key).max_by { |i| i }
182
+ end
183
+
184
+ # Allows to specify an order attribute:
185
+ #
186
+ # <#ActiveEnumerable>.order('name')
187
+ #
188
+ # <#ActiveEnumerable>.order(:name)
189
+ def order(key)
190
+ __new_relation__(all.sort_by { |item| MethodCaller.new(item).call(key) })
191
+ end
192
+
193
+ # Reverse the existing order clause on the relation.
194
+ #
195
+ # <#ActiveEnumerable>.order('name').reverse_order
196
+ def reverse_order
197
+ __new_relation__(to_a.reverse)
198
+ end
199
+
200
+ # Returns a chainable relation with zero records.
201
+ #
202
+ # Any subsequent condition chained to the returned relation will continue
203
+ # generating an empty relation.
204
+ #
205
+ # Used in cases where a method or scope could return zero records but the
206
+ # result needs to be chainable.
207
+ #
208
+ # For example:
209
+ #
210
+ # @posts = current_user.visible_posts.where(name: params[:name])
211
+ # # => the visible_posts method is expected to return a chainable Relation
212
+ #
213
+ # def visible_posts
214
+ # case role
215
+ # when 'Country Manager'
216
+ # <#ActiveEnumerable>.where(country: country)
217
+ # when 'Reviewer'
218
+ # <#ActiveEnumerable>.published
219
+ # when 'Bad User'
220
+ # <#ActiveEnumerable>.none # It can't be chained if [] is returned.
221
+ # end
222
+ # end
223
+ #
224
+ def none
225
+ __new_relation__([])
226
+ end
227
+
228
+ private
229
+
230
+ def values_by_key(key)
231
+ all.map { |obj| obj.send(key) }
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveEnumerable
2
+ class RecordNotFound < StandardError
3
+ end
4
+ end
@@ -0,0 +1,52 @@
1
+ module ActiveEnumerable
2
+ module Scopes
3
+ def method_missing(meth, *args, &block)
4
+ if create_scope_method(meth)
5
+ send(meth, *args, &block)
6
+ else
7
+ super
8
+ end
9
+ end
10
+
11
+ def respond_to_missing?(meth, include_private = false)
12
+ if create_scope_method(meth)
13
+ true
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ def create_scope_method(meth)
20
+ if (scope = self.class.__scoped_methods__.find { |a| a.first == meth })
21
+ self.define_singleton_method(scope.first) do
22
+ scope(&scope.last)
23
+ end
24
+ end
25
+ end
26
+
27
+ def scope(&block)
28
+ result = instance_exec(&block)
29
+ if result.is_a? Array
30
+ __new_relation__(result)
31
+ else
32
+ result
33
+ end
34
+ end
35
+
36
+ private :create_scope_method
37
+
38
+ module ClassMethods
39
+ def scope(name, block)
40
+ __scoped_methods__ << [name, block]
41
+ end
42
+
43
+ def __scoped_methods__
44
+ @__scoped_methods__ ||= []
45
+ end
46
+ end
47
+
48
+ def self.included(base)
49
+ base.extend(ClassMethods)
50
+ end
51
+ end
52
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveEnumerable
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -0,0 +1,99 @@
1
+ module ActiveEnumerable
2
+ module Where
3
+ class WhereNotChain
4
+ def initialize(collection, parent_class)
5
+ @collection = collection
6
+ @parent_class = parent_class
7
+ end
8
+
9
+ # Returns a new relation expressing WHERE + NOT condition according to
10
+ # the conditions in the arguments.
11
+ #
12
+ # #not accepts conditions as a string, array, or hash. See Where#where for
13
+ # more details on each format.
14
+ #
15
+ # <#ActiveEnumerable>.where.not(name: "Jon")
16
+ # <#ActiveEnumerable>.where.not(name: nil)
17
+ # <#ActiveEnumerable>.where.not(name: %w(Ko1 Nobu))
18
+ # <#ActiveEnumerable>.where.not(name: "Jon", role: "admin")
19
+ def not(conditions={})
20
+ @parent_class.call(@collection.reject do |record|
21
+ Finder.new(record).is_of(conditions)
22
+ end)
23
+ end
24
+ end
25
+
26
+ # Returns a new relation, which is the result of filtering the current relation
27
+ # according to the conditions in the arguments.
28
+ #
29
+ # === hash
30
+ #
31
+ # #where will accept a hash condition, in which the keys are fields and the values
32
+ # are values to be searched for.
33
+ #
34
+ # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
35
+ #
36
+ # <#ActiveEnumerable>.where({ name: "Joe", email: "joe@example.com" })
37
+ #
38
+ # <#ActiveEnumerable>.where({ name: ["Alice", "Bob"]})
39
+ #
40
+ # <#ActiveEnumerable>.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
41
+ #
42
+ # <#ActiveEnumerable>.where(contracts:[{ created_at: (Time.now.midnight - 1.day)..Time.now.midnight }])
43
+ #
44
+ # .or
45
+ #
46
+ # Returns a new relation, which is the logical union of this relation and the one passed as an
47
+ # argument.
48
+ #
49
+ # The two relations must be structurally compatible: they must be scoping the same model, and
50
+ # they must differ only by #where.
51
+ #
52
+ # <#ActiveEnumerable>.where(id: 1).or(<#ActiveEnumerable>.where(author_id: 3))
53
+ #
54
+ # Additional conditions can be passed to where in hash form.
55
+ #
56
+ # <#ActiveEnumerable>.where(id: 1).or(author_id: 3)
57
+ #
58
+ def where(conditions=nil)
59
+ return WhereNotChain.new(all, method(:__new_relation__)) if conditions.nil?
60
+ enable_or create_where_relation(conditions, to_a.select do |record|
61
+ Finder.new(record).is_of(conditions)
62
+ end)
63
+ end
64
+
65
+ def enable_or(relation)
66
+ pre_where_to_a = to_a
67
+ relation.define_singleton_method(:or) do |conditions_or_relation|
68
+ conditions = get_conditions(conditions_or_relation)
69
+ or_result = create_where_relation(where_conditions, pre_where_to_a).where(conditions)
70
+ create_where_relation(or_result.where_conditions, relation.to_a.concat(or_result.to_a).uniq)
71
+ end
72
+ relation
73
+ end
74
+
75
+ private :enable_or
76
+
77
+ def get_conditions(conditions_or_relation)
78
+ if conditions_or_relation.respond_to?(:where_conditions)
79
+ conditions_or_relation.where_conditions
80
+ else
81
+ conditions_or_relation
82
+ end
83
+ end
84
+
85
+ private :get_conditions
86
+
87
+ def where_conditions
88
+ @where_conditions ||= {}
89
+ end
90
+
91
+ def create_where_relation(conditions, array)
92
+ nr = __new_relation__(array)
93
+ nr.where_conditions.merge!(conditions)
94
+ nr
95
+ end
96
+
97
+ private :create_where_relation
98
+ end
99
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_enumerable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dustin Zeisler
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-01-01 00:00:00.000000000 Z
11
+ date: 2016-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -72,7 +72,16 @@ files:
72
72
  - bin/console
73
73
  - bin/setup
74
74
  - lib/active_enumerable.rb
75
+ - lib/active_enumerable/base.rb
76
+ - lib/active_enumerable/comparable.rb
77
+ - lib/active_enumerable/enumerable.rb
78
+ - lib/active_enumerable/finder.rb
79
+ - lib/active_enumerable/method_caller.rb
80
+ - lib/active_enumerable/queries.rb
81
+ - lib/active_enumerable/record_not_found.rb
82
+ - lib/active_enumerable/scopes.rb
75
83
  - lib/active_enumerable/version.rb
84
+ - lib/active_enumerable/where.rb
76
85
  homepage: https://github.com/zeisler/active_enumerable
77
86
  licenses:
78
87
  - MIT
@@ -93,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
102
  version: '0'
94
103
  requirements: []
95
104
  rubyforge_project:
96
- rubygems_version: 2.4.5.1
105
+ rubygems_version: 2.2.5
97
106
  signing_key:
98
107
  specification_version: 4
99
108
  summary: Gives ActiveRecord like querying methods to ruby enumerable objects.