elastic_queue 0.0.10 → 0.0.11

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: 3b98fbf9af561bf1f2bdaf418941c235e9efb6d5
4
- data.tar.gz: 2a2b8517d4cdb107ad9fdb55ace575741ee5854f
3
+ metadata.gz: 5f6a30f14c2ff7bb0330c854ea5653d295d4f24b
4
+ data.tar.gz: 6aeca9a70bf31dc9b65b0c9862dc835c45535ca2
5
5
  SHA512:
6
- metadata.gz: 6ebcfd62bf72d135e4fa2532d61237996698a8974b99cf2bdfb9293a6705c0f52dc23af9aa6b2e6e46f861d49a711699c5c9b753b9b96c45c72e2c6c5ddcb035
7
- data.tar.gz: e7a20b9650fbd4e411e6449d8373a32416c9a9af9257aeec5caa16827a4faa16985d072d132ba7e5be25893a5d104debace1d5385e2fbf7d684961116b4e59f8
6
+ metadata.gz: 71487d775f4a86d6f9f17abb25173d671b7a153769bdf906e4bd100deede733efec86c85235388e84954a39239448d3afdef7d5c9d5a801dcbd48ab0f47ff990
7
+ data.tar.gz: 4e17e151ca7cb3885eea1f496e283d5cd0f4c6532b1d56d67c34469bd4205875eb071c1322c3bc712ba6ca7e1d36e86e9b8bd8fc8269ea586b369c6da007641d
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ tmp
17
17
  .yardoc
18
18
  _yardoc
19
19
  doc/
20
+ spec/eq_test.db
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- elastic_queue (0.0.10)
4
+ elastic_queue (0.0.11)
5
+ activerecord (~> 3.0)
5
6
  activesupport (~> 3.0)
6
7
  elasticsearch (~> 0.4)
7
8
  will_paginate (~> 3.0)
@@ -9,9 +10,26 @@ PATH
9
10
  GEM
10
11
  remote: https://rubygems.org/
11
12
  specs:
12
- activesupport (3.2.17)
13
+ activemodel (3.2.18)
14
+ activesupport (= 3.2.18)
15
+ builder (~> 3.0.0)
16
+ activerecord (3.2.18)
17
+ activemodel (= 3.2.18)
18
+ activesupport (= 3.2.18)
19
+ arel (~> 3.0.2)
20
+ tzinfo (~> 0.3.29)
21
+ activesupport (3.2.18)
13
22
  i18n (~> 0.6, >= 0.6.4)
14
23
  multi_json (~> 1.0)
24
+ arel (3.0.3)
25
+ builder (3.0.4)
26
+ columnize (0.8.9)
27
+ debugger (1.6.6)
28
+ columnize (>= 0.3.1)
29
+ debugger-linecache (~> 1.2.0)
30
+ debugger-ruby_core_source (~> 1.3.2)
31
+ debugger-linecache (1.2.0)
32
+ debugger-ruby_core_source (1.3.2)
15
33
  diff-lcs (1.2.5)
16
34
  elasticsearch (0.4.11)
17
35
  elasticsearch-api (= 0.4.11)
@@ -26,7 +44,7 @@ GEM
26
44
  faraday (0.9.0)
27
45
  multipart-post (>= 1.2, < 3)
28
46
  i18n (0.6.9)
29
- multi_json (1.9.2)
47
+ multi_json (1.10.0)
30
48
  multipart-post (2.0.0)
31
49
  rake (10.3.1)
32
50
  rspec (2.14.1)
@@ -38,15 +56,18 @@ GEM
38
56
  diff-lcs (>= 1.1.3, < 2.0)
39
57
  rspec-mocks (2.14.6)
40
58
  sqlite3 (1.3.9)
59
+ tzinfo (0.3.39)
41
60
  will_paginate (3.0.5)
42
61
 
43
62
  PLATFORMS
44
63
  ruby
45
64
 
46
65
  DEPENDENCIES
66
+ activesupport (~> 3.0)
47
67
  bundler (~> 1.0)
68
+ debugger (~> 1.6)
48
69
  elastic_queue!
49
70
  factory_girl (~> 4.0)
50
71
  rake (~> 10.0)
51
72
  rspec (~> 2.6)
52
- sqlite3
73
+ sqlite3 (~> 1.3)
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.test_files = Dir['spec/**/*.rb']
20
20
 
21
21
  s.add_runtime_dependency 'activesupport', '~> 3.0'
22
+ s.add_runtime_dependency 'activerecord', '~>3.0'
22
23
  s.add_runtime_dependency 'elasticsearch', '~> 0.4'
23
24
  s.add_runtime_dependency 'will_paginate', '~> 3.0'
24
25
  s.add_development_dependency 'bundler', '~> 1.0'
@@ -26,4 +27,5 @@ Gem::Specification.new do |s|
26
27
  s.add_development_dependency 'factory_girl', '~> 4.0'
27
28
  s.add_development_dependency 'sqlite3', '~> 1.3'
28
29
  s.add_development_dependency 'rake', '~> 10.0'
29
- end
30
+ s.add_development_dependency 'debugger', '~>1.6'
31
+ end
@@ -9,7 +9,7 @@ module ElasticQueue
9
9
  include Percolation
10
10
 
11
11
  def self.search_client
12
- Elasticsearch::Client.new hosts: ElasticQueue::OPTIONS[:elasticsearch_hosts]
12
+ @search_client ||= Elasticsearch::Client.new hosts: ElasticQueue::OPTIONS[:elasticsearch_hosts]
13
13
  end
14
14
 
15
15
  def self.models(*models)
@@ -12,18 +12,27 @@ module ElasticQueue
12
12
  private
13
13
 
14
14
  def option_to_filter(key, value)
15
- if value.is_a? Array
15
+ # return and_options(value) if key == :and
16
+ if [:or, :and].include?(key)
17
+ join_options(key, value)
18
+ elsif value.is_a? Array
16
19
  or_filter(key, value)
17
20
  elsif value.is_a? Hash
18
- # date?
19
- time_filter(key, value)
21
+ comparison_filter(key, value)
20
22
  elsif value.nil?
23
+ # e.g. name: nil
21
24
  null_filter(key, value)
22
25
  else
26
+ # e.g. status: 'fresh'
23
27
  term_filter(key, value)
24
28
  end
25
29
  end
26
30
 
31
+ def join_options(operator, options)
32
+ conditions = options.map { |o| options_to_filters(o) }.flatten
33
+ { operator => conditions }
34
+ end
35
+
27
36
  def or_filter(term, values)
28
37
  # flatten here because ranges return arrays
29
38
  conditions = values.map { |v| option_to_filter(term, v) }.flatten
@@ -35,7 +44,7 @@ module ElasticQueue
35
44
  end
36
45
 
37
46
  # take something like follow_up: { before: 'hii', after: 'low' }
38
- def time_filter(term, value)
47
+ def comparison_filter(term, value)
39
48
  value.map do |k, v|
40
49
  comparator = k.to_sym.in?([:after, :greater_than, :gt]) ? :gt : :lt
41
50
  range_filter(term, v, comparator)
@@ -13,7 +13,7 @@ module ElasticQueue
13
13
  end
14
14
 
15
15
  def create_index
16
- search_client.indices.create index: index_name
16
+ search_client.indices.create index: index_name, body: default_index_settings
17
17
  add_mappings
18
18
  end
19
19
 
@@ -26,12 +26,14 @@ module ElasticQueue
26
26
  search_client.indices.refresh index: index_name
27
27
  end
28
28
 
29
- def bulk_index(batch_size = 10_000)
29
+ # you can pass scopes into bulk_index to be used when fetching records
30
+ # bulk_index(scopes: { some_model: [:scope1, :scope2], some_other_model: [:scope3] }) will fetch SomeModel.scope1.scope2 and SomeOtherModel.scope3 and index only those records.
31
+ def bulk_index(scopes: {}, batch_size: 10_000)
30
32
  create_index unless index_exists?
31
33
  model_classes.each do |klass|
32
34
  # modelclass(model).includes(associations_for_index(model)).
33
35
  index_type = klass.to_s.underscore
34
- klass.find_in_batches(batch_size: batch_size) do |batch|
36
+ scoped_class(klass, scopes).find_in_batches(batch_size: batch_size) do |batch|
35
37
  body = []
36
38
  batch.each do |instance|
37
39
  body << { index: { _index: index_name, _id: instance.id, _type: index_type, data: instance.indexed_for_queue } }
@@ -41,9 +43,34 @@ module ElasticQueue
41
43
  end
42
44
  end
43
45
 
46
+ def scoped_class(klass, scopes)
47
+ return klass unless scopes[klass.to_s.underscore.to_sym]
48
+ scopes[klass.to_s.underscore.to_sym].each do |scope|
49
+ klass = klass.send(scope)
50
+ end
51
+ klass
52
+ end
53
+
54
+ def default_index_settings
55
+ {
56
+ settings: {
57
+ analysis: {
58
+ analyzer: {
59
+ default: {
60
+ type: :custom,
61
+ tokenizer: :whitespace,
62
+ filter: [:lowercase]
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ end
69
+
44
70
  def add_mappings
45
71
  model_classes.each do |klass|
46
- search_client.indices.put_mapping index: index_name, type: klass.to_s.underscore, body: klass.queue_mapping
72
+ mapping = klass.queue_mapping
73
+ search_client.indices.put_mapping index: index_name, type: klass.to_s.underscore, body: mapping if mapping.present?
47
74
  end
48
75
  end
49
76
 
@@ -45,40 +45,39 @@ module ElasticQueue
45
45
 
46
46
  def paginate(options = {})
47
47
  options.each { |k, v| @options.send("#{k}=", v) }
48
- all.paginate
48
+ Results.new(@queue, execute(paginate: true), @options).paginate
49
49
  end
50
50
 
51
+ # TODO: remove if not using, add per_page if using
51
52
  def page=(page)
52
53
  @options.page = (page)
53
54
  end
54
55
 
55
56
  def all
56
- @results ||= Results.new(@queue, execute, @options)
57
+ Results.new(@queue, execute, @options).instantiated_queue_items
57
58
  end
58
59
 
59
60
  # return just the ids of the records (useful when combined with SQL queries)
60
61
  def ids
61
- results = execute
62
- results[:hits][:hits].map { |h| h[:_source][:id] }
62
+ execute[:hits][:hits].map { |h| h[:_source][:id] }
63
63
  end
64
64
 
65
65
  def count
66
- res = execute(count: true)
67
- res[:hits][:total].to_i
66
+ execute(count: true, paginate: false)[:hits][:total].to_i
68
67
  end
69
68
 
70
- def execute(count: false)
69
+ private
70
+
71
+ def execute(count: false, paginate: false)
71
72
  begin
72
- search = execute_query(count: false)
73
- search = substitute_page(search) if !count && search['hits']['hits'].length == 0 && search['hits']['total'] != 0
73
+ search = paginate ? execute_paginated_query : execute_all_query( count: count )
74
+ search = substitute_page(search) if paginate && !count && search['hits']['hits'].length == 0 && search['hits']['total'] != 0
74
75
  rescue Elasticsearch::Transport::Transport::Errors::BadRequest
75
76
  search = failed_search
76
77
  end
77
78
  search.with_indifferent_access
78
79
  end
79
80
 
80
- private
81
-
82
81
  # this allows you to chain scopes
83
82
  # the 2+ scopes in the chain will be called
84
83
  # on a query object and not on the base object
@@ -89,16 +88,21 @@ module ElasticQueue
89
88
  end
90
89
  end
91
90
 
92
- def execute_query(count: false)
93
- search_type = count ? 'count' : 'query_then_fetch'
94
- @queue.search_client.search index: @queue.index_name, body: body, search_type: search_type, from: @options.from, size: @options.per_page
91
+ def execute_all_query(count: false)
92
+ record_count = @queue.search_client.search index: @queue.index_name, body: body, search_type: 'count'
93
+ return record_count if count
94
+ @queue.search_client.search index: @queue.index_name, body: body, search_type: 'query_then_fetch', from: 0, size: record_count['hits']['total'].to_i
95
+ end
96
+
97
+ def execute_paginated_query
98
+ @queue.search_client.search index: @queue.index_name, body: body, search_type: 'query_then_fetch', from: @options.from, size: @options.per_page
95
99
  end
96
100
 
97
101
  def substitute_page(search)
98
102
  total_hits = search['hits']['total'].to_i
99
103
  per_page = @options.per_page
100
104
  @options.page = (total_hits / per_page.to_f).ceil
101
- execute_query
105
+ execute_paginated_query
102
106
  end
103
107
 
104
108
  def failed_search
@@ -9,6 +9,7 @@ module ElasticQueue
9
9
  end
10
10
 
11
11
  module ClassMethods
12
+
12
13
  def queues(*queues)
13
14
  @queues ||= queues
14
15
  end
@@ -2,9 +2,8 @@ require 'will_paginate/collection'
2
2
 
3
3
  module ElasticQueue
4
4
  class Results
5
- attr_reader :paginate
6
5
 
7
- delegate :empty?, :each, :total_entries, :total_pages, :current_page, to: :paginate
6
+ attr_reader :instantiated_queue_items
8
7
 
9
8
  def initialize(queue, search_results, query_options)
10
9
  @queue = queue
@@ -12,19 +11,22 @@ module ElasticQueue
12
11
  @start = query_options.page
13
12
  @per_page = query_options.per_page
14
13
  @total = search_results[:hits][:total]
15
- @paginate = WillPaginate::Collection.create(@start, @per_page, @total) do |pager|
14
+ end
15
+
16
+ def paginate
17
+ WillPaginate::Collection.create(@start, @per_page, @total) do |pager|
16
18
  pager.replace(@instantiated_queue_items)
17
19
  end
18
20
  end
19
21
 
22
+ private
23
+
20
24
  def instantiate_queue_items(search_results)
21
25
  grouped_results, sort_order = group_sorted_results(search_results)
22
26
  records = fetch_records(grouped_results)
23
27
  sort_records(records, sort_order)
24
28
  end
25
29
 
26
- private
27
-
28
30
  # group the results by { class_name: [ids] } and save their sorted order
29
31
  def group_sorted_results(search_results)
30
32
  grouped_results = {}
@@ -1,3 +1,3 @@
1
1
  module ElasticQueue
2
- VERSION = '0.0.10'
2
+ VERSION = '0.0.11'
3
3
  end
data/spec/factories.rb CHANGED
@@ -1,64 +0,0 @@
1
- FactoryGirl.define do
2
- sequence :email_address do |n|
3
- "testy#{n}@example.com"
4
- end
5
-
6
- sequence :user_id do
7
- (10000...10005).to_a.sample
8
- end
9
-
10
- sequence :agent_fee_sales_session_status do
11
- ['active', 'dead'].sample
12
- end
13
-
14
- factory :agent_fee_sales_session do
15
- agent
16
- status { generate(:agent_fee_sales_session_status) }
17
- assigned_to { generate(:user_id) }
18
- assigned_at Time.parse('2013-12-02 08:40:33')
19
- expires_at Time.parse('2014-06-02 08:40:33')
20
- follow_up Time.parse('2013-12-17 06:00:00')
21
- hot '1'
22
- priority 'high'
23
- created_at Time.parse('2013-11-18 14:36:05')
24
- updated_at Time.parse('2013-12-17 11:31:08')
25
- factory :null_follow_up_agent_fee_sales_session do
26
- follow_up nil
27
- end
28
- end
29
-
30
- factory :agent do
31
- after(:build) { |agent| agent.class.skip_callback(:save, :after, :notate_changes) }
32
- user
33
- company
34
- status 'active'
35
- name_on_license 'Testy'
36
- license_type 'Salesperson'
37
- license_number '111111'
38
- license_state 'CA'
39
- broker_name 'Ali'
40
- years_in_real_estate '12 years'
41
- end
42
-
43
- factory :company do
44
- name 'Flywheel'
45
- address '233 Post st.'
46
- city 'San Francisco'
47
- state 'CA'
48
- zip '94104'
49
- end
50
-
51
- factory :user do
52
- after(:build) { |user| user.class.skip_callback(:save, :after, :notate_changes) }
53
- email { generate(:email_address) }
54
- login { |u| u.email }
55
- password '1234567'
56
- first_name 'Testy'
57
- last_name 'Testerson'
58
- phone_office '415-555-5555'
59
- factory :agent_user do
60
- user_type 'agent'
61
- end
62
- end
63
-
64
- end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ElasticQueue::Base integration' do
4
+ describe '#search_client' do
5
+ it 'returns an Elasticsearch::Transport::Client' do
6
+ expect(ElasticQueue::Base.search_client).to be_a Elasticsearch::Transport::Client
7
+ end
8
+
9
+ it 'returns the same client every time' do
10
+ expect(ElasticQueue::Base.search_client.object_id).to eq ElasticQueue::Base.search_client.object_id
11
+ end
12
+ end
13
+
14
+ describe '#models, also tests(#tell_models, #model_names, #model_classes)' do
15
+ it '#models sets @models and tells the model about itself' do
16
+ class Cannibal < ActiveRecord::Base
17
+ include ElasticQueue::Queueable
18
+ end
19
+ # Cannibal.stub(:add_queue)
20
+ # Cannibal.should_receive(:add_queue).with(:"elastic_queue/base")
21
+ ElasticQueue::Base.models(:cannibal)
22
+ expect(ElasticQueue::Base.instance_variable_get('@models')).to eq [:cannibal]
23
+ end
24
+ end
25
+
26
+ describe '#index_name, #index_name =' do
27
+ pending('trivial')
28
+ end
29
+
30
+ describe '#eager_load' do
31
+ pending
32
+ end
33
+
34
+ describe '#eager_loads' do
35
+ pending
36
+ end
37
+
38
+ describe '#scopes' do
39
+ pending
40
+ end
41
+
42
+ describe '#scopes' do
43
+ pending
44
+ end
45
+
46
+ describe '#default_scope' do
47
+ pending
48
+ end
49
+
50
+ describe '#query' do
51
+ pending
52
+ end
53
+
54
+ describe '#filter' do
55
+ pending
56
+ end
57
+
58
+ describe '#count' do
59
+ pending
60
+ end
61
+
62
+ describe '#paginate' do
63
+ pending('not implemented yet')
64
+ end
65
+
66
+ describe 'instance #query' do
67
+ pending
68
+ end
69
+
70
+ end
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ElasticQueue::Filters integration' do
4
+ before :all do
5
+ class Animal < ActiveRecord::Base
6
+ include ElasticQueue::Queueable
7
+ queues :test_animals_queue
8
+ queue_attributes :dangerous, :cute, :birthdate, :name
9
+ not_analyzed_queue_attributes :species, :description
10
+ end
11
+
12
+ class TestAnimalsQueue < ElasticQueue::Base
13
+ models :animal
14
+ end
15
+
16
+ TestAnimalsQueue.create_index
17
+
18
+ @create_animals = -> {
19
+ Animal.create({ name: 'a', birthdate: Date.today.at_midnight - 1.year })
20
+ Animal.create({ name: 'b', birthdate: Date.today.at_midnight - 2.years })
21
+ Animal.create({ name: 'c', birthdate: Date.today.at_midnight - 3.years })
22
+ }
23
+ end
24
+
25
+ after :all do
26
+ [:Animal, :TestAnimalsQueue].each do |constant|
27
+ Object.send(:remove_const, constant)
28
+ end
29
+ delete_index('test_animals_queue')
30
+ end
31
+
32
+ describe 'ElasticQueue::Query#filter' do
33
+ after :each do
34
+ Animal.all.each(&:destroy)
35
+ end
36
+
37
+ it 'can filter on one value' do
38
+ @create_animals.call
39
+ expect(TestAnimalsQueue.query.filter(name: 'a').all.map(&:name)).to eq ['a']
40
+ end
41
+
42
+ it 'can filter by a less than or greater than a time' do
43
+ @create_animals.call
44
+ expect(TestAnimalsQueue.query.filter(birthdate: { after: Date.today - 1.year - 1.day }).all.map(&:name)).to eq ['a']
45
+ expect(TestAnimalsQueue.query.filter(birthdate: { before: Date.today - 2.years - 1.day }).all.map(&:name)).to eq ['c']
46
+ end
47
+
48
+ it 'can filter by a less than and greater than a time' do
49
+ @create_animals.call
50
+ expect(TestAnimalsQueue.query.filter(birthdate: { after: Date.today - 2.year - 1.day, before: Date.today - 1.year - 1.day}).all.map(&:name)).to eq ['b']
51
+ end
52
+
53
+ it 'can filter by less than or greater than a string' do
54
+ @create_animals.call
55
+ expect(TestAnimalsQueue.query.filter(name: { after: 'a', before: 'c' }).all.map(&:name)).to eq ['b']
56
+ end
57
+
58
+ it 'doesn\'t error if you try to filter on an nonexistent value' do
59
+ @create_animals.call
60
+ expect(TestAnimalsQueue.query.filter(likes_peanut_butter: true).all.map(&:name)).to eq []
61
+ end
62
+
63
+ it 'filters underscored values as one word' do
64
+ Animal.create({ name: 'pin_head' })
65
+ Animal.create({ name: 'pin' })
66
+ expect(TestAnimalsQueue.query.filter(name: 'pin').all.map(&:name)).to eq ['pin']
67
+ end
68
+
69
+ it 'treats parentheses as letters' do
70
+ Animal.create({ name: 'Sr. Honks-a-lot' , species: '(Silly) Goose' })
71
+ expect(TestAnimalsQueue.query.filter(species: 'Goose').all.map(&:name)).to eq []
72
+ expect(TestAnimalsQueue.query.filter(species: '(Silly) Goose').all.map(&:name)).to eq ['Sr. Honks-a-lot']
73
+ end
74
+
75
+ it 'automatically joins multiple filter values with an OR' do
76
+ @create_animals.call
77
+ expect(TestAnimalsQueue.query.filter(name: ['a', 'b']).all.map(&:name).sort).to eq ['a', 'b']
78
+ end
79
+
80
+ it 'defaults to joining multiple filter keys with an AND' do
81
+ Animal.create({ name: 'x', species: 'dog' })
82
+ Animal.create({ name: 'y', species: 'dog' })
83
+ expect(TestAnimalsQueue.query.filter(name: 'x').filter(species: 'dog').all.map(&:name)).to eq ['x']
84
+ end
85
+
86
+ it 'can join multiple filter keys with an OR' do
87
+ Animal.create({ name: 'x', species: 'dog' })
88
+ Animal.create({ name: 'y', species: 'cat' })
89
+ Animal.create({ name: 'z', species: 'rat' })
90
+ expect(TestAnimalsQueue.query.filter(or: [{ name: 'x' }, { species: 'cat' }]).all.map(&:name).sort).to eq ['x', 'y']
91
+ end
92
+
93
+ it 'can join multiple filter values with multiple filter keys with an OR' do
94
+ Animal.create({ name: 'x', species: 'dog' })
95
+ Animal.create({ name: 'y', species: 'cat' })
96
+ Animal.create({ name: 'z', species: 'chicken' })
97
+ Animal.create({ name: 'a', species: 'chicken' })
98
+ expect(TestAnimalsQueue.query.filter(or: [{ name: 'z' }, { species: ['cat', 'dog'] }]).all.map(&:name).sort).to eq ['x', 'y', 'z']
99
+ end
100
+
101
+ it 'can nest multiple ands and ors' do
102
+ Animal.create({ name: 'rusty', species: 'dog', dangerous: false })
103
+ Animal.create({ name: 'killer', species: 'mountain lion', dangerous: true })
104
+ Animal.create({ name: 'cock-a-doodle-doo', species: 'chicken', dangerous: false })
105
+ Animal.create({ name: 'old bess', species: 'cow', dangerous: true })
106
+ Animal.create({ name: 'speedy', species: 'horse', dangerous: false })
107
+ expect(TestAnimalsQueue.query.filter({
108
+ # (rusty, killer, speedy) && ( rusty || lucky, killer, old bess, cock-a-doodle-doo)
109
+ name: ['rusty', 'killer', 'speedy'], #(rusty, killer, speedy) && (
110
+ or: [
111
+ and: [ # rusty ||
112
+ { species: ['dog', 'mountain lion'] },
113
+ { dangerous: false }
114
+ ],
115
+ or: [ #speedy, killer, old bess, cock-a-doodle-doo )
116
+ { species: ['horse', 'chicken'] },
117
+ { dangerous: true }
118
+ ]
119
+ ]
120
+ }).all.map(&:name).sort).to eq ['killer', 'rusty', 'speedy']
121
+ end
122
+ end
123
+ end