active_enumerable 0.1.1 → 0.2.0

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: 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
  - - ">="