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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 016716ccb116b9441ef93f1d0698201c9b41ac50
4
- data.tar.gz: a164e13d533c2aa282b9d7663ee65f1a28b8ea17
3
+ metadata.gz: f474e8fd2a0f244864a65b57e314e9a10b0a4ebf
4
+ data.tar.gz: 86960a5df4654c9d4301f37d9238614e1ac894d0
5
5
  SHA512:
6
- metadata.gz: 7ab10d01e6738b89b9f375b1a1df9156f5cf8c2a3f96b026e698f244c232fd73a9188adc3523d678c721bb995b5902620e0b04dc4647c6fb90148e26f3112cb1
7
- data.tar.gz: cb3a52fd97b0aae04319d7749e8df84b23fa9b30747cbc54c861f90b9c0d009ee6ba695e6dfb6a4db185fe541999e524ce5d72b86c799afc94a7ebdf4aac4c47
6
+ metadata.gz: 0184271b0c54d92a5d495a54a25fcbb78d9f9f68d0c51ad515f08477982ff64cedaf9fd0811c82e00976093156a5af2ef332f25c9d310550b51f87f549002a7c
7
+ data.tar.gz: 98a7c9613270176e506222bbfc447e3f5bbd3e45a2311d763884c956e03e584e8fef1dad54cf4e0e5ded48331836ff6ab993b2a429af59fc467a3f2419582203
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --format documentation
2
2
  --color
3
+ --require spec_helper
@@ -1,4 +1,8 @@
1
1
  language: ruby
2
+ sudo: false
3
+ cache: bundler
2
4
  rvm:
5
+ - 2.1.7
3
6
  - 2.2.3
7
+ - 2.3.0
4
8
  before_install: gem install bundler -v 1.10.6
@@ -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
+ [![Build Status](https://travis-ci.org/zeisler/active_enumerable.svg?branch=master)](https://travis-ci.org/zeisler/active_enumerable)
4
+ [![Gem Version](https://badge.fury.io/rb/active_enumerable.svg)](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
@@ -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"
@@ -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
- @to_a = collection.to_ary
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
- attr_reader :to_a
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
- to_a << item
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
@@ -3,7 +3,7 @@ module ActiveEnumerable
3
3
  include ::Enumerable
4
4
 
5
5
  def each(*args, &block)
6
- @to_a.send(:each, *args, &block)
6
+ @collection.send(:each, *args, &block)
7
7
  end
8
8
  end
9
9
  end
@@ -1,16 +1,49 @@
1
1
  module ActiveEnumerable
2
- # @private
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? ::Enumerable
13
- any_match(col, match)
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 any_match(col, match)
32
- match.any? { |m| compare(col, m) }
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) == match
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, KeyError => e
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
- 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)
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)
@@ -0,0 +1,12 @@
1
+ module ActiveEnumerable
2
+ module ScopeMethod
3
+ def scope(&block)
4
+ result = instance_exec(&block)
5
+ if result.is_a? Array
6
+ __new_relation__(result)
7
+ else
8
+ result
9
+ end
10
+ end
11
+ end
12
+ end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ActiveEnumerable
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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.1.1
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-05 00:00:00.000000000 Z
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: '0'
99
+ version: '2.1'
98
100
  required_rubygems_version: !ruby/object:Gem::Requirement
99
101
  requirements:
100
102
  - - ">="