active_enumerable 0.1.1 → 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/.rspec +1 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +8 -0
- data/README.md +32 -0
- data/active_enumerable.gemspec +2 -0
- data/lib/active_enumerable.rb +4 -0
- data/lib/active_enumerable/base.rb +14 -4
- data/lib/active_enumerable/english_dsl.rb +81 -0
- data/lib/active_enumerable/enumerable.rb +1 -1
- data/lib/active_enumerable/finder.rb +51 -6
- data/lib/active_enumerable/method_caller.rb +3 -1
- data/lib/active_enumerable/queries.rb +12 -7
- data/lib/active_enumerable/scope_method.rb +12 -0
- data/lib/active_enumerable/scopes.rb +1 -9
- data/lib/active_enumerable/version.rb +1 -1
- data/lib/active_enumerable/where.rb +1 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f474e8fd2a0f244864a65b57e314e9a10b0a4ebf
|
4
|
+
data.tar.gz: 86960a5df4654c9d4301f37d9238614e1ac894d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0184271b0c54d92a5d495a54a25fcbb78d9f9f68d0c51ad515f08477982ff64cedaf9fd0811c82e00976093156a5af2ef332f25c9d310550b51f87f549002a7c
|
7
|
+
data.tar.gz: 98a7c9613270176e506222bbfc447e3f5bbd3e45a2311d763884c956e03e584e8fef1dad54cf4e0e5ded48331836ff6ab993b2a429af59fc467a3f2419582203
|
data/.rspec
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## 0.2.0 - 2015-02-15
|
5
|
+
### Features
|
6
|
+
- English like querying DSL
|
7
|
+
|
8
|
+
### Enhancement
|
9
|
+
- `#where` can match when arrays are equal.
|
10
|
+
- `#where` can match when strings with regex.
|
11
|
+
|
4
12
|
## 0.1.0 - 2015-12-31
|
5
13
|
This version is only to secure the rubygem name.
|
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# ActiveEnumerable
|
2
2
|
|
3
|
+
[](https://travis-ci.org/zeisler/active_enumerable)
|
4
|
+
[](https://badge.fury.io/rb/active_enumerable)
|
5
|
+
|
3
6
|
Include ActiveRecord like query methods to Ruby enumerable collections.
|
4
7
|
|
5
8
|
## Installation
|
@@ -20,6 +23,8 @@ Or install it yourself as:
|
|
20
23
|
|
21
24
|
## Usage
|
22
25
|
|
26
|
+
### ActiveRecord Like Querying
|
27
|
+
|
23
28
|
```ruby
|
24
29
|
require "active_enumerable"
|
25
30
|
|
@@ -46,6 +51,33 @@ Customers.item_class = Customer
|
|
46
51
|
|
47
52
|
customers.create({paid: true, credit: 1500}).to_a.last
|
48
53
|
#=> <#Customer paid: true, credit: 1500>
|
54
|
+
|
55
|
+
```
|
56
|
+
|
57
|
+
### English Like DSL
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
class People
|
61
|
+
include ActiveEnumerable
|
62
|
+
|
63
|
+
scope :unpaid, -> { where(paid: false).or(credit: 0) }
|
64
|
+
end
|
65
|
+
|
66
|
+
people = People.new([{ name: "Reuben" }, { name: "Naomi" }])
|
67
|
+
people.where { has(:name).of("Reuben") } }
|
68
|
+
#=> <#People [{ name: "Reuben" }]]
|
69
|
+
|
70
|
+
|
71
|
+
people = People.new( [
|
72
|
+
{ name: "Reuben", parents: [{ name: "Mom", age: 29 }, { name: "Dad", age: 33 }] },
|
73
|
+
{ name: "Naomi", parents: [{ name: "Mom", age: 29 }, { name: "Dad", age: 41 }] }
|
74
|
+
] )
|
75
|
+
|
76
|
+
people.where { has(:parents).of(age: 29, name: "Mom").or(age: 33, name: "Dad") }
|
77
|
+
#=> <#People [{ name: "Reuben", parents: [...] }, { name: "Naomi", parents: [...] }]
|
78
|
+
|
79
|
+
people.where { has(:parents).of(age: 29, name: "Mom").and(age: 33, name: "Dad")
|
80
|
+
#=> <#People [{ name: "Reuben", parents: [...] }>
|
49
81
|
```
|
50
82
|
|
51
83
|
## Development
|
data/active_enumerable.gemspec
CHANGED
@@ -19,6 +19,8 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
+
spec.required_ruby_version = '>= 2.1'
|
23
|
+
|
22
24
|
spec.add_development_dependency "bundler", "~> 1.10"
|
23
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
26
|
spec.add_development_dependency "rspec", "~> 3.4"
|
data/lib/active_enumerable.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require "active_enumerable/version"
|
2
2
|
require "active_enumerable/base"
|
3
|
+
require "active_enumerable/record_not_found"
|
3
4
|
require "active_enumerable/comparable"
|
4
5
|
require "active_enumerable/enumerable"
|
5
6
|
require "active_enumerable/finder"
|
6
7
|
require "active_enumerable/method_caller"
|
8
|
+
require "active_enumerable/scope_method"
|
7
9
|
require "active_enumerable/scopes"
|
8
10
|
require "active_enumerable/where"
|
9
11
|
require "active_enumerable/queries"
|
12
|
+
require "active_enumerable/english_dsl"
|
10
13
|
|
11
14
|
module ActiveEnumerable
|
12
15
|
include Base
|
@@ -14,6 +17,7 @@ module ActiveEnumerable
|
|
14
17
|
include Comparable
|
15
18
|
include Queries
|
16
19
|
include Scopes
|
20
|
+
include EnglishDsl
|
17
21
|
|
18
22
|
module ClassMethods
|
19
23
|
include Scopes::ClassMethods
|
@@ -1,10 +1,16 @@
|
|
1
1
|
module ActiveEnumerable
|
2
2
|
module Base
|
3
3
|
def initialize(collection=[])
|
4
|
-
|
4
|
+
if collection.is_a? ::Enumerator::Lazy
|
5
|
+
@collection = collection
|
6
|
+
else
|
7
|
+
@collection = collection.to_a
|
8
|
+
end
|
5
9
|
end
|
6
10
|
|
7
|
-
|
11
|
+
def to_a
|
12
|
+
@collection.to_a
|
13
|
+
end
|
8
14
|
|
9
15
|
# @private
|
10
16
|
def __new_relation__(collection)
|
@@ -20,11 +26,15 @@ module ActiveEnumerable
|
|
20
26
|
end
|
21
27
|
|
22
28
|
def add(item)
|
23
|
-
|
29
|
+
@collection << item
|
24
30
|
end
|
25
31
|
|
26
32
|
def all
|
27
|
-
self
|
33
|
+
self.tap { to_a }
|
34
|
+
end
|
35
|
+
|
36
|
+
def name
|
37
|
+
self.class.name
|
28
38
|
end
|
29
39
|
|
30
40
|
module ClassMethods
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module ActiveEnumerable
|
2
|
+
module EnglishDsl
|
3
|
+
include ScopeMethod
|
4
|
+
include Where
|
5
|
+
|
6
|
+
# @param [Hash]
|
7
|
+
# @yield takes block to evaluate English Dsl
|
8
|
+
# <ActiveEnumerable>#where{ has(:name).of("Dustin") }
|
9
|
+
# @return [ActiveEnumerable]
|
10
|
+
def where(*args, &block)
|
11
|
+
if block_given?
|
12
|
+
scope(&block).send(:_english_eval__)
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [String, Symbol] attr is either a method name a Hash key.
|
19
|
+
# has(:name)
|
20
|
+
def has(attr)
|
21
|
+
all_conditions << [attr]
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [Hash, Object] matches is list of sub conditions or associations to query.
|
26
|
+
# Or this can by any value to compare the result from attr.
|
27
|
+
def of(matches)
|
28
|
+
if all_conditions.empty? || !(all_conditions.last.count == 1)
|
29
|
+
raise ".has(attr) must be call before calling #of."
|
30
|
+
else
|
31
|
+
all_conditions.last << matches
|
32
|
+
self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# After calling #of(matches) providing additional matches to #or(matches) build a either or query.
|
37
|
+
# @param [Hash, Object] matches is list of sub conditions or associations to query.
|
38
|
+
# Or this can by any value to compare the result from attr.
|
39
|
+
def or(matches)
|
40
|
+
raise ".has(attr).of(matches) must be call before calling #or(matches)." if all_conditions.empty? || !(all_conditions.last.count == 2)
|
41
|
+
evaluation_results << english_where
|
42
|
+
all_conditions.last[1] = matches
|
43
|
+
evaluation_results << english_where
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [Hash, Object, NilClass] matches is list of sub conditions or associations to query.
|
48
|
+
# Or this can by any value to compare the result from attr.
|
49
|
+
# Or passing nothing and provide a different has(attr)
|
50
|
+
# has(attr).of(matches).and.has(other_attr)
|
51
|
+
def and(matches=nil)
|
52
|
+
if matches
|
53
|
+
all_conditions.last[1].merge!(matches)
|
54
|
+
evaluation_results << english_where
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def all_conditions
|
62
|
+
@all_conditions ||= []
|
63
|
+
end
|
64
|
+
|
65
|
+
def _english_eval__
|
66
|
+
if evaluation_results.empty?
|
67
|
+
english_where
|
68
|
+
else
|
69
|
+
__new_relation__ evaluation_results.flat_map(&:to_a).uniq
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def english_where
|
74
|
+
where all_conditions.each_with_object({}) { |e, h| h[e[0]] = e[1] }
|
75
|
+
end
|
76
|
+
|
77
|
+
def evaluation_results
|
78
|
+
@evaluation_results ||= []
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -1,16 +1,49 @@
|
|
1
1
|
module ActiveEnumerable
|
2
|
-
|
2
|
+
|
3
3
|
class Finder
|
4
4
|
def initialize(record)
|
5
5
|
@method_caller = MethodCaller.new(record)
|
6
6
|
end
|
7
7
|
|
8
|
+
# Regex conditions
|
9
|
+
# Finder.new({ name: "Timmy" }).is_of({ name: /Tim/ })
|
10
|
+
# #=> true
|
11
|
+
#
|
12
|
+
# Hash conditions
|
13
|
+
# record = { name: "Timmy", parents: [{ name: "Dad", age: 33 }, { name: "Mom", age: 29 }] } }
|
14
|
+
#
|
15
|
+
# Matching array of partial hashes identities
|
16
|
+
# Finder.new(record).is_of(parents: [{ name: "Dad" }, { name: "Mom" }]))
|
17
|
+
# #=> true
|
18
|
+
#
|
19
|
+
# Matching partial hashes identities to an array of hashes
|
20
|
+
# Finder.new(record).is_of(parents: { name: "Dad", age: 33 })
|
21
|
+
# #=> true
|
22
|
+
#
|
23
|
+
# Array conditions
|
24
|
+
# record = { name: "Timmy" }
|
25
|
+
#
|
26
|
+
# Finder.new(record).is_of(name: %w(Timmy Fred))
|
27
|
+
# #=> true
|
28
|
+
# Finder.new(record).is_of(name: ["Sammy", /Tim/])
|
29
|
+
# #=> true
|
30
|
+
#
|
31
|
+
# Value conditions
|
32
|
+
# record = { name: "Timmy", age: 10 }
|
33
|
+
#
|
34
|
+
# Finder.new(record).is_of(name: "Timmy")
|
35
|
+
# #=> true
|
36
|
+
# Finder.new(record).is_of(age: 10)
|
37
|
+
# #=> true
|
38
|
+
#
|
39
|
+
# @param [Hash] conditions
|
40
|
+
# @return [true, false]
|
8
41
|
def is_of(conditions={})
|
9
42
|
conditions.all? do |col, match|
|
10
43
|
if match.is_a? Hash
|
11
44
|
hash_match(col, match)
|
12
|
-
elsif match.is_a?
|
13
|
-
|
45
|
+
elsif match.is_a? Array
|
46
|
+
array_match(col, match)
|
14
47
|
else
|
15
48
|
compare(col, match)
|
16
49
|
end
|
@@ -28,12 +61,24 @@ module ActiveEnumerable
|
|
28
61
|
end
|
29
62
|
end
|
30
63
|
|
31
|
-
def
|
32
|
-
|
64
|
+
def array_match(col, match)
|
65
|
+
if @method_caller.call(col).is_a? Array
|
66
|
+
if !(r = compare(col, match)) && match.map(&:class).uniq == [Hash]
|
67
|
+
match.all? { |m| hash_match(col, m) }
|
68
|
+
else
|
69
|
+
r
|
70
|
+
end
|
71
|
+
else
|
72
|
+
match.any? { |m| compare(col, m) }
|
73
|
+
end
|
33
74
|
end
|
34
75
|
|
35
76
|
def compare(col, match)
|
36
|
-
@method_caller.call(col)
|
77
|
+
@method_caller.call(col).public_send(compare_by(match), match)
|
78
|
+
end
|
79
|
+
|
80
|
+
def compare_by(match)
|
81
|
+
(match.is_a? Regexp) ? :=~ : :==
|
37
82
|
end
|
38
83
|
end
|
39
84
|
end
|
@@ -14,8 +14,10 @@ module ActiveEnumerable
|
|
14
14
|
else
|
15
15
|
object.public_send(method)
|
16
16
|
end
|
17
|
-
rescue NoMethodError
|
17
|
+
rescue NoMethodError => e
|
18
18
|
raise e if raise_no_method
|
19
|
+
rescue KeyError => e
|
20
|
+
raise e, "#{e.message} for #{object}" if raise_no_method
|
19
21
|
end
|
20
22
|
end
|
21
23
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveEnumerable
|
2
2
|
module Queries
|
3
|
-
include ActiveEnumerable::Where
|
4
3
|
|
5
4
|
# 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
5
|
# If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
|
@@ -12,13 +11,17 @@ module ActiveEnumerable
|
|
12
11
|
# <#ActiveEnumerable>.find([1]) # returns an array for the object with ID = 1
|
13
12
|
#
|
14
13
|
# <tt>ActiveEnumerable::RecordNotFound</tt> will be raised if one or more ids are not found.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
# @return [ActiveEnumerable, Object]
|
15
|
+
# @param [*Fixnum, Array<Fixnum>] args
|
16
|
+
def find(*args)
|
17
|
+
raise RecordNotFound.new("Couldn't find #{self.name} without an ID") if args.compact.empty?
|
18
|
+
if args.count > 1 || args.first.is_a?(Array)
|
19
|
+
__new_relation__(args.flatten.lazy.map do |id|
|
20
|
+
find_by!(id: id.to_i)
|
21
|
+
end)
|
22
|
+
else
|
23
|
+
find_by!(id: args.first.to_i)
|
19
24
|
end
|
20
|
-
return __new_relation__(results) if ids.class == Array
|
21
|
-
results.first
|
22
25
|
end
|
23
26
|
|
24
27
|
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
@@ -74,6 +77,8 @@ module ActiveEnumerable
|
|
74
77
|
# If no record is found, returns <tt>nil</tt>.
|
75
78
|
#
|
76
79
|
# <#ActiveEnumerable>.find_by name: 'Spartacus', rating: 4
|
80
|
+
#
|
81
|
+
# # @see ActiveEnumerable::Finder#is_of for all usages of conditions.
|
77
82
|
def find_by(conditions = {})
|
78
83
|
to_a.detect do |record|
|
79
84
|
Finder.new(record).is_of(conditions)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module ActiveEnumerable
|
2
2
|
module Scopes
|
3
|
+
include ScopeMethod
|
3
4
|
def method_missing(meth, *args, &block)
|
4
5
|
if create_scope_method(meth)
|
5
6
|
send(meth, *args, &block)
|
@@ -24,15 +25,6 @@ module ActiveEnumerable
|
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
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
28
|
private :create_scope_method
|
37
29
|
|
38
30
|
module ClassMethods
|
@@ -55,6 +55,7 @@ module ActiveEnumerable
|
|
55
55
|
#
|
56
56
|
# <#ActiveEnumerable>.where(id: 1).or(author_id: 3)
|
57
57
|
#
|
58
|
+
# @see ActiveEnumerable::Finder#is_of for all usages of conditions.
|
58
59
|
def where(conditions=nil)
|
59
60
|
return WhereNotChain.new(all, method(:__new_relation__)) if conditions.nil?
|
60
61
|
enable_or create_where_relation(conditions, to_a.select do |record|
|
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.
|
4
|
+
version: 0.2.0
|
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-02-
|
11
|
+
date: 2016-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -74,11 +74,13 @@ files:
|
|
74
74
|
- lib/active_enumerable.rb
|
75
75
|
- lib/active_enumerable/base.rb
|
76
76
|
- lib/active_enumerable/comparable.rb
|
77
|
+
- lib/active_enumerable/english_dsl.rb
|
77
78
|
- lib/active_enumerable/enumerable.rb
|
78
79
|
- lib/active_enumerable/finder.rb
|
79
80
|
- lib/active_enumerable/method_caller.rb
|
80
81
|
- lib/active_enumerable/queries.rb
|
81
82
|
- lib/active_enumerable/record_not_found.rb
|
83
|
+
- lib/active_enumerable/scope_method.rb
|
82
84
|
- lib/active_enumerable/scopes.rb
|
83
85
|
- lib/active_enumerable/version.rb
|
84
86
|
- lib/active_enumerable/where.rb
|
@@ -94,7 +96,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
96
|
requirements:
|
95
97
|
- - ">="
|
96
98
|
- !ruby/object:Gem::Version
|
97
|
-
version: '
|
99
|
+
version: '2.1'
|
98
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
101
|
requirements:
|
100
102
|
- - ">="
|