CloudSesame 0.6.8 → 0.7.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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +12 -0
  3. data/.gitignore +1 -1
  4. data/Gemfile.lock +13 -13
  5. data/Guardfile +1 -0
  6. data/cloud_sesame.gemspec +2 -2
  7. data/coverage/.last_run.json +1 -1
  8. data/coverage/.resultset.json +679 -591
  9. data/lib/cloud_sesame/config/credential.rb +6 -6
  10. data/lib/cloud_sesame/context.rb +0 -5
  11. data/lib/cloud_sesame/domain/base.rb +147 -128
  12. data/lib/cloud_sesame/domain/client.rb +25 -25
  13. data/lib/cloud_sesame/domain/client_module/caching/base.rb +19 -23
  14. data/lib/cloud_sesame/domain/client_module/caching/no_cache.rb +11 -11
  15. data/lib/cloud_sesame/domain/client_module/caching/rails_cache.rb +32 -28
  16. data/lib/cloud_sesame/domain/client_module/caching.rb +19 -28
  17. data/lib/cloud_sesame/domain/client_module/retry.rb +6 -6
  18. data/lib/cloud_sesame/domain/config.rb +6 -6
  19. data/lib/cloud_sesame/query/ast/abstract/multi_expression_operator.rb +37 -0
  20. data/lib/cloud_sesame/query/ast/abstract/operator.rb +27 -0
  21. data/lib/cloud_sesame/query/ast/abstract/single_expression_operator.rb +39 -0
  22. data/lib/cloud_sesame/query/ast/abstract/value.rb +88 -0
  23. data/lib/cloud_sesame/query/ast/and.rb +1 -1
  24. data/lib/cloud_sesame/query/ast/date_value.rb +19 -3
  25. data/lib/cloud_sesame/query/ast/literal.rb +13 -28
  26. data/lib/cloud_sesame/query/ast/near.rb +4 -4
  27. data/lib/cloud_sesame/query/ast/not.rb +6 -7
  28. data/lib/cloud_sesame/query/ast/numeric_value.rb +20 -12
  29. data/lib/cloud_sesame/query/ast/or.rb +1 -1
  30. data/lib/cloud_sesame/query/ast/phrase.rb +1 -1
  31. data/lib/cloud_sesame/query/ast/prefix.rb +1 -1
  32. data/lib/cloud_sesame/query/ast/range_value.rb +40 -39
  33. data/lib/cloud_sesame/query/ast/root.rb +2 -6
  34. data/lib/cloud_sesame/query/ast/string_value.rb +26 -0
  35. data/lib/cloud_sesame/query/ast/term.rb +1 -1
  36. data/lib/cloud_sesame/query/ast/value.rb +25 -54
  37. data/lib/cloud_sesame/query/builder.rb +83 -54
  38. data/lib/cloud_sesame/query/domain/block.rb +3 -1
  39. data/lib/cloud_sesame/query/dsl/applied_filter_query.rb +14 -23
  40. data/lib/cloud_sesame/query/dsl/field_accessors.rb +25 -22
  41. data/lib/cloud_sesame/query/dsl/field_array_methods.rb +1 -1
  42. data/lib/cloud_sesame/query/dsl/inspect_method.rb +31 -0
  43. data/lib/cloud_sesame/query/dsl/sort_methods.rb +5 -4
  44. data/lib/cloud_sesame/query/node/abstract.rb +1 -2
  45. data/lib/cloud_sesame/query/node/facet.rb +1 -5
  46. data/lib/cloud_sesame/query/node/filter_query.rb +15 -2
  47. data/lib/cloud_sesame/query/node/fuzziness.rb +4 -3
  48. data/lib/cloud_sesame/query/node/page.rb +2 -2
  49. data/lib/cloud_sesame/query/node/query.rb +4 -17
  50. data/lib/cloud_sesame/query/node/query_options.rb +6 -16
  51. data/lib/cloud_sesame/query/node/query_options_field.rb +3 -1
  52. data/lib/cloud_sesame/query/node/query_parser.rb +8 -10
  53. data/lib/cloud_sesame/query/node/request.rb +57 -31
  54. data/lib/cloud_sesame/query/node/sort.rb +25 -11
  55. data/lib/cloud_sesame.rb +15 -12
  56. data/lib/{abstract_object.rb → services/abstract_object.rb} +0 -0
  57. data/lib/services/class_specific.rb +44 -0
  58. data/lib/services/lazy_object.rb +19 -0
  59. data/spec/cloud_sesame/domain/base_spec.rb +26 -11
  60. data/spec/cloud_sesame/domain/client_module/caching/base_spec.rb +2 -1
  61. data/spec/cloud_sesame/domain/client_module/caching/no_cache_spec.rb +2 -3
  62. data/spec/cloud_sesame/domain/client_module/caching/rails_cache_spec.rb +6 -6
  63. data/spec/cloud_sesame/domain/client_module/caching_spec.rb +33 -32
  64. data/spec/cloud_sesame/query/ast/abstract/multi_expression_operator_spec.rb +67 -0
  65. data/spec/cloud_sesame/query/ast/abstract/operator_spec.rb +29 -0
  66. data/spec/cloud_sesame/query/ast/abstract/single_expression_operator_spec.rb +70 -0
  67. data/spec/cloud_sesame/query/ast/abstract/value_spec.rb +118 -0
  68. data/spec/cloud_sesame/query/ast/and_spec.rb +1 -1
  69. data/spec/cloud_sesame/query/ast/or_spec.rb +1 -1
  70. data/spec/cloud_sesame/query/ast/range_value_spec.rb +5 -11
  71. data/spec/cloud_sesame/query/builder_spec.rb +10 -4
  72. data/spec/cloud_sesame/query/domain/block_spec.rb +15 -8
  73. data/spec/cloud_sesame/query/dsl/{block_methods_spec.rb → block_styled_operators_spec.rb} +0 -0
  74. data/spec/cloud_sesame/query/dsl/field_accessors_spec.rb +88 -50
  75. data/spec/cloud_sesame/query/dsl/field_array_methods_spec.rb +0 -2
  76. data/spec/cloud_sesame/query/node/facet_spec.rb +1 -15
  77. data/spec/cloud_sesame/query/node/filter_query_spec.rb +16 -2
  78. data/spec/cloud_sesame/query/node/page_spec.rb +3 -2
  79. data/spec/cloud_sesame/query/node/query_options_spec.rb +1 -1
  80. data/spec/cloud_sesame/query/node/query_parser_spec.rb +1 -7
  81. data/spec/cloud_sesame/query/node/query_spec.rb +10 -28
  82. data/spec/cloud_sesame/query/node/sort_spec.rb +19 -28
  83. data/spec/cloud_sesame_spec.rb +2 -156
  84. data/spec/helpers/benchmark_helper.rb +12 -0
  85. data/spec/integration/filter_query_spec.rb +0 -0
  86. data/spec/profiling_spec.rb +155 -0
  87. data/spec/{abstract_object_spec.rb → services/abstract_object_spec.rb} +0 -0
  88. data/spec/services/class_specific_spec.rb +135 -0
  89. data/spec/spec_helper.rb +3 -0
  90. metadata +32 -22
  91. data/lib/active_support/core_ext/object/deep_dup.rb +0 -53
  92. data/lib/active_support/core_ext/object/duplicable.rb +0 -98
  93. data/lib/cloud_sesame/query/ast/multi_expression_operator.rb +0 -35
  94. data/lib/cloud_sesame/query/ast/operator.rb +0 -25
  95. data/lib/cloud_sesame/query/ast/single_expression_operator.rb +0 -35
  96. data/lib/cloud_sesame/query/scope.rb +0 -21
  97. data/profiler.rb +0 -91
  98. data/spec/cloud_sesame/query/ast/multi_expression_operator_spec.rb +0 -76
  99. data/spec/cloud_sesame/query/ast/operator_spec.rb +0 -29
  100. data/spec/cloud_sesame/query/ast/single_expression_operator_spec.rb +0 -78
  101. data/spec/context_spec.rb +0 -17
@@ -5,30 +5,12 @@ module CloudSesame
5
5
  module Node
6
6
  describe Sort do
7
7
 
8
- let(:arguments) {{ }}
9
- let(:node) { Sort.new(arguments) }
10
-
11
- describe '#initialize' do
12
- context 'when arguments passed in' do
13
- let(:arguments) {{ sort: "score asc,price desc" }}
14
- it 'should accept sort argument and initialize sorting_attributes' do
15
- expect(node.sorting_attributes).to eq({ score: :asc, price: :desc })
16
- end
17
- it 'should initialize an empty sorting_attributes when arguments sort is empty' do
18
- arguments[:sort] = ""
19
- expect(node.sorting_attributes).to eq({ })
20
- end
21
- end
22
- context 'when arguments not passed in' do
23
- it 'should initialize an empty sorting_attributes' do
24
- expect(node.sorting_attributes).to eq({ })
25
- end
26
- end
27
- end
8
+ let(:context) {{ fields: {} }}
9
+ let(:node) { Sort.new(context) }
28
10
 
29
11
  describe '#[]' do
30
12
  context 'with existing attribute' do
31
- let(:arguments) {{ sort: "score asc,price desc" }}
13
+ before { node.attributes = { score: :asc, price: :desc }}
32
14
  it 'should return the sorting order of the attribute' do
33
15
  expect(node[:score]).to eq :asc
34
16
  expect(node[:price]).to eq :desc
@@ -44,7 +26,7 @@ module CloudSesame
44
26
 
45
27
  describe '#[]=' do
46
28
  context 'with existing attribute' do
47
- let(:arguments) {{ sort: "score asc,price desc" }}
29
+ before { node.attributes = { score: :asc, price: :desc }}
48
30
  it 'should override the sorting order of the attribute' do
49
31
  expect{ node[:score] = :desc }.to change{ node[:score] }.from(:asc).to(:desc)
50
32
  end
@@ -56,16 +38,25 @@ module CloudSesame
56
38
  end
57
39
  end
58
40
 
59
- describe '#run' do
41
+ describe '#compile' do
60
42
  context 'with existing sorting attributes' do
61
- let(:arguments) {{ sort: "score asc,price desc" }}
62
- it 'should return a hash with serialized sort attributes' do
63
- expect(node.compile).to eq arguments
43
+ context 'when context contains aliase field' do
44
+ let(:context) {{ fields: { aliased: { as: :real_name } } }}
45
+ before { node.attributes = { aliased: :asc, price: :desc }}
46
+ it 'should return serialized sort attributes with actual field name' do
47
+ expect(node.compile).to eq "real_name asc,price desc"
48
+ end
49
+ end
50
+ context 'when context contains no alias field' do
51
+ before { node.attributes = { score: :asc, price: :desc }}
52
+ it 'should return serialized sort attributes' do
53
+ expect(node.compile).to eq "score asc,price desc"
54
+ end
64
55
  end
65
56
  end
66
57
  context 'with empty sorting attributes' do
67
- it 'should return an empty hash' do
68
- expect(node.compile).to eq({})
58
+ it 'should return nil' do
59
+ expect(node.compile).to eq nil
69
60
  end
70
61
  end
71
62
  end
@@ -1,158 +1,4 @@
1
- # require 'spec_helper'
2
- # require 'ruby-prof'
3
- # require 'benchmark'
1
+ describe CloudSesame do
4
2
 
5
- # describe CloudSesame do
6
3
 
7
- # # FAKE RAILS AND RAILS CACHE
8
- # # =====================================
9
- # class ::Rails
10
- # def self.cache
11
- # @cache ||= FakeCache.new
12
- # end
13
- # end
14
-
15
- # class ::FakeCache
16
- # def table
17
- # @table ||= {}
18
- # end
19
- # def fetch(key, &block)
20
- # table[key] ||= (block.call if block_given?)
21
- # end
22
- # end
23
-
24
- # # AWS initializer
25
- # # =======================================================
26
- # require 'yaml'
27
- # YAML.load_file('aws.yml').each do |key, value|
28
- # ENV["AWS_#{ key }"] = value
29
- # end
30
-
31
- # # Domain Initializer /config/initializers/cloudsearch.rb
32
- # # =======================================================
33
- # require 'cloud_sesame'
34
-
35
- # CloudSesame::Domain::Client.configure do |config|
36
- # config.access_key = ENV['AWS_ACCESS_KEY_ID']
37
- # config.secret_key = ENV['AWS_SECRET_ACCESS_KEY']
38
- # end
39
-
40
- # # Usage Example
41
- # # =======================================================
42
- # class Product
43
- # include CloudSesame
44
-
45
- # def self.greeting
46
- # "hello world!"
47
- # end
48
-
49
- # define_cloudsearch do
50
- # # Product CloudSesame Config
51
- # config.endpoint = ENV['AWS_ENDPOINT']
52
- # config.region = ENV['AWS_REGION']
53
-
54
- # caching_with :RailsCache
55
-
56
- # default_size 100
57
-
58
- # define_sloppiness 3
59
-
60
- # define_fuzziness do
61
- # max_fuzziness 3
62
- # min_char_size 6
63
- # fuzzy_percent 0.17
64
- # end
65
-
66
- # field :searchable_text, query: { weight: 2 }
67
- # field :description, query: true
68
- # field :tags
69
-
70
- # field :affiliate_advertiser_ext_id, facet: { size: 50 }
71
- # field :currency, facet: true
72
- # field :discount_percentage, facet: { buckets: %w([10,100] [25,100] [50,100] [70,100]), method: 'interval' }
73
- # field :manufacturer_name, facet: { size: 50 }
74
- # field :price, facet: { buckets: %w([0,25] [25,50] [50,100] [100,200] [200,}), method: 'interval' }
75
- # field :category_string, facet: { sort: 'bucket', size: 10_000 }
76
- # field :created_at
77
-
78
- # end
79
-
80
- # end
81
-
82
- # class Coupon
83
- # include CloudSesame
84
-
85
- # VALID_COUPON_RANK = 20
86
-
87
- # define_cloudsearch do
88
- # config.endpoint = ENV['AWS_ENDPOINT']
89
- # config.region = ENV['AWS_REGION']
90
-
91
- # default_size 10
92
-
93
- # define_sloppiness 3
94
-
95
- # define_fuzziness do
96
- # max_fuzziness 3
97
- # min_char_size 6
98
- # fuzzy_percent 0.17
99
- # end
100
-
101
- # field :end_date, as: :date1
102
- # field :rank, as: :num1, default: -> { gte VALID_COUPON_RANK }
103
- # field :searchable_text, query: true
104
- # field :affiliate_advertiser_search_ext_id, as: :string1, facet: { size: 50 }
105
- # field :countries, as: :string_array1
106
- # field :deal_types, as: :string_array2, facet: {}
107
- # field :tags, as: :string_array3, facet: {}
108
- # field :type, default: -> { 'Catalog::CouponSearchable' }
109
- # end
110
-
111
- # end
112
-
113
- # # @tags = [1, 2]
114
- # # n = 10_000
115
- # # q = nil
116
- # # result = RubyProf.profile do
117
- # # n.times do
118
- # q = Product.cloudsearch.query("black jacket").sort(price: :asc).page(1).size(1000)
119
- # .price { gt 100 }
120
- # .and {
121
- # or! {
122
- # tags *@tags
123
- # tags
124
- # tags nil
125
- # and! {
126
- # tags.not "3", "4"
127
- # }
128
- # and!.not {
129
- # tags.start_with "5", "6"
130
- # tags.not.start_with("7")
131
- # tags.not.near("8", distance: 7)
132
- # tags start_with("9"), near("10")
133
- # tags term("11", boost: 2)
134
- # tags.not phrase "12"
135
- # }
136
- # or!.not {
137
- # price(25..100)
138
- # price 100...200
139
- # price gte(200).lt(300)
140
- # price gte(300)
141
- # }
142
- # or! {
143
- # created_at Date.today - 7
144
- # created_at gte(Date.today)
145
- # created_at gte(Date.today).lt(Date.today + 3)
146
- # }
147
- # }
148
- # }
149
-
150
- # # end
151
- # # end
152
- # # printer = RubyProf::FlatPrinter.new(result)
153
- # # printer.print(STDOUT, {})
154
-
155
- # binding.pry
156
-
157
-
158
- # end
4
+ end
@@ -0,0 +1,12 @@
1
+ require 'ruby-prof'
2
+ require 'benchmark'
3
+
4
+ module BenchmarkHelper
5
+
6
+ def profile(n, &block)
7
+ result = RubyProf.profile { n.times(&block) }
8
+ printer = RubyProf::FlatPrinter.new(result)
9
+ printer.print(STDOUT, {})
10
+ end
11
+
12
+ end
File without changes
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+
3
+ fdescribe CloudSesame do
4
+ # FAKE RAILS AND RAILS CACHE
5
+ # =====================================
6
+ class ::Rails
7
+ def self.cache
8
+ @cache ||= FakeCache.new
9
+ end
10
+ end
11
+
12
+ class ::FakeCache
13
+ def table
14
+ @table ||= {}
15
+ end
16
+ def fetch(key, &block)
17
+ table[key] ||= (block.call if block_given?)
18
+ end
19
+ end
20
+
21
+ # AWS initializer
22
+ # =======================================================
23
+ require 'yaml'
24
+ YAML.load_file('aws.yml').each do |key, value|
25
+ ENV["AWS_#{ key }"] = value
26
+ end
27
+
28
+ # Domain Initializer /config/initializers/cloudsearch.rb
29
+ # =======================================================
30
+ require 'cloud_sesame'
31
+
32
+ CloudSesame::Domain::Client.configure do |config|
33
+ config.access_key = ENV['AWS_ACCESS_KEY_ID']
34
+ config.secret_key = ENV['AWS_SECRET_ACCESS_KEY']
35
+ end
36
+
37
+ # Usage Example
38
+ # =======================================================
39
+ class Product
40
+ include CloudSesame
41
+
42
+ def self.greeting
43
+ "hello world!"
44
+ end
45
+
46
+ define_cloudsearch do
47
+ # Product CloudSesame Config
48
+ config.endpoint = ENV['AWS_ENDPOINT']
49
+ config.region = ENV['AWS_REGION']
50
+
51
+ caching_with :RailsCache
52
+
53
+ default_size 100
54
+
55
+ define_sloppiness 3
56
+
57
+ define_fuzziness do
58
+ max_fuzziness 3
59
+ min_char_size 6
60
+ fuzzy_percent 0.17
61
+ end
62
+
63
+ field :searchable_text, query: { weight: 2 }, type: :string
64
+ field :description, query: true, type: :string
65
+ field :tags, type: :string
66
+
67
+ field :affiliate_advertiser_ext_id, facet: { size: 50 }
68
+ field :currency, facet: true
69
+ field :discount_percentage, facet: { buckets: %w([10,100] [25,100] [50,100] [70,100]), method: 'interval' }
70
+ field :manufacturer_name, facet: { size: 50 }
71
+ field :price, facet: { buckets: %w([0,25] [25,50] [50,100] [100,200] [200,}), method: 'interval' }
72
+ field :category_string, facet: { sort: 'bucket', size: 10_000 }
73
+ field :created_at, type: :datetime
74
+
75
+ end
76
+
77
+ end
78
+
79
+ class Coupon
80
+ include CloudSesame
81
+
82
+ VALID_COUPON_RANK = 20
83
+
84
+ define_cloudsearch do
85
+ config.endpoint = ENV['AWS_ENDPOINT']
86
+ config.region = ENV['AWS_REGION']
87
+
88
+ default_size 10
89
+
90
+ define_sloppiness 3
91
+
92
+ define_fuzziness do
93
+ max_fuzziness 3
94
+ min_char_size 6
95
+ fuzzy_percent 0.17
96
+ end
97
+
98
+ field :end_date, as: :date1
99
+ field :rank, as: :num1, default: -> { gte VALID_COUPON_RANK }
100
+ field :searchable_text, query: true
101
+ field :affiliate_advertiser_search_ext_id, as: :string1, facet: { size: 50 }
102
+ field :countries, as: :string_array1
103
+ field :deal_types, as: :string_array2, facet: {}
104
+ field :tags, as: :string_array3, facet: {}
105
+ field :type, default: -> { 'Catalog::CouponSearchable' }
106
+ end
107
+
108
+ end
109
+
110
+ @tags = [1, 2]
111
+ q = nil
112
+ profile 1 do
113
+ q = Product.cloudsearch
114
+ .query("black leather jacket")
115
+ .sort(price: :asc, created_at: :desc)
116
+ .page(3)
117
+ .size(1000)
118
+ .price { gt 1 }
119
+ .tags { "2" }
120
+ .and {
121
+ or! {
122
+ tags *@tags
123
+ tags nil
124
+ and! {
125
+ tags.not 3, 4
126
+ }
127
+ and!.not {
128
+ tags.start_with "5", "6"
129
+ tags.not.start_with("7")
130
+ tags.not.near("8", distance: 7)
131
+ tags start_with("9"), near("10")
132
+ tags term("11", boost: 2)
133
+ tags.not phrase "12"
134
+ }
135
+ or!.not {
136
+ price(25..100)
137
+ price 100...200
138
+ price gte(200).lt(300)
139
+ price gte(300)
140
+ }
141
+ or! {
142
+ created_at Date.today - 7
143
+ created_at gte(Date.today)
144
+ created_at gte(Date.today).lt(Date.today + 3)
145
+ }
146
+ }
147
+ }
148
+
149
+ q.compile
150
+
151
+ end
152
+
153
+ binding.pry
154
+
155
+ end
@@ -0,0 +1,135 @@
1
+ describe ClassSpecific do
2
+
3
+ class SearchableClass; end
4
+
5
+ let(:searchable) { :SearchableClass }
6
+
7
+ describe '.construct_class' do
8
+ let(:specific_class) { SearchableClass }
9
+ let(:klass) { Class.new { extend ClassSpecific } }
10
+
11
+ context 'when sub-class has not being defined previously' do
12
+ context 'and specific class passed in has a constant name' do
13
+ it 'should define a sub-class under the extended class' do
14
+ expect{ klass.construct_class(specific_class) }.to change{ klass.constants.size }.by(1)
15
+ end
16
+ it 'should name the sub-class after the class passed in' do
17
+ expect{ klass.construct_class(specific_class) }.to change{ klass.constants }.by([searchable])
18
+ end
19
+ end
20
+
21
+ context 'and specific class passed in does not have constant name' do
22
+ let(:specific_class) { Class.new }
23
+ it 'should not define a sub-class under the extended class' do
24
+ expect{ klass.construct_class(specific_class) }.to_not change{ klass.constants.size }
25
+ end
26
+ end
27
+
28
+ context 'and callback is given' do
29
+ context 'and callback args is not given' do
30
+ it 'should invoke callback after construct' do
31
+ expect do |b|
32
+ klass.after_construct(&b)
33
+ klass.construct_class(specific_class)
34
+ end.to yield_with_args(specific_class)
35
+ end
36
+ end
37
+ context 'and callback is not given' do
38
+ let(:callback_args) { [1, 2, 3] }
39
+ it 'should invoke callback and yield callback args after construct' do
40
+ expect do |b|
41
+ klass.after_construct(&b)
42
+ klass.construct_class(specific_class, callback_args: callback_args)
43
+ end.to yield_with_args(specific_class, *callback_args)
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ context 'when sub-class has being defined previously' do
51
+ it 'should not define a sub-class again' do
52
+ klass.construct_class(specific_class)
53
+ expect(Class).to_not receive(:new).with(klass)
54
+ expect{ klass.construct_class(specific_class) }.to_not change{ klass.constants.size }
55
+ end
56
+ it 'should not invoke callback twice' do
57
+ expect do |b|
58
+ klass.after_construct(&b)
59
+ klass.construct_class(specific_class)
60
+ klass.construct_class(specific_class)
61
+ end.to yield_control.exactly(1).times
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ describe '.construct_module' do
68
+ let(:specific_class) { SearchableClass }
69
+ let(:custom_module) { Module.new { extend ClassSpecific } }
70
+
71
+ context 'when sub-class has not being defined previously' do
72
+ context 'and specific class passed in has a constant name' do
73
+ it 'should define a sub-class under the extended class' do
74
+ expect{ custom_module.construct_module(specific_class) }.to change{ custom_module.constants.size }.by(1)
75
+ end
76
+ it 'should name the sub-class after the class passed in' do
77
+ expect{ custom_module.construct_module(specific_class) }.to change{ custom_module.constants }.by([searchable])
78
+ end
79
+ end
80
+
81
+ context 'and specific class passed in does not have constant name' do
82
+ let(:specific_class) { Class.new }
83
+ it 'should not define a sub-class under the extended class' do
84
+ expect{ custom_module.construct_module(specific_class) }.to_not change{ custom_module.constants.size }
85
+ end
86
+ end
87
+
88
+ context 'and callback is given' do
89
+ context 'and callback args is not given' do
90
+ it 'should invoke callback after construct' do
91
+ expect do |b|
92
+ custom_module.after_construct(&b)
93
+ custom_module.construct_module(specific_class)
94
+ end.to yield_with_args(specific_class)
95
+ end
96
+ end
97
+ context 'and callback is not given' do
98
+ let(:callback_args) { [1, 2, 3] }
99
+ it 'should invoke callback and yield callback args after construct' do
100
+ expect do |b|
101
+ custom_module.after_construct(&b)
102
+ custom_module.construct_module(specific_class, callback_args: callback_args)
103
+ end.to yield_with_args(specific_class, *callback_args)
104
+ end
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ context 'when sub-class has being defined previously' do
111
+ it 'should not define a sub-class again' do
112
+ custom_module.construct_module(specific_class)
113
+ expect(Module).to_not receive(:new).with(custom_module)
114
+ expect{ custom_module.construct_module(specific_class) }.to_not change{ custom_module.constants.size }
115
+ end
116
+ it 'should not invoke callback twice' do
117
+ expect do |b|
118
+ custom_module.after_construct(&b)
119
+ custom_module.construct_module(specific_class)
120
+ custom_module.construct_module(specific_class)
121
+ end.to yield_control.exactly(1).times
122
+ end
123
+ end
124
+ end
125
+
126
+ describe '.after_construct' do
127
+ let(:specific_class) { SearchableClass }
128
+ let(:klass) { Class.new { extend ClassSpecific } }
129
+ let(:block) { Proc.new {} }
130
+ it 'should store the block as __construct_callback__' do
131
+ expect{ klass.after_construct(&block) }.to change{ klass.instance_variable_get(:@__construct_callback__) }.from(nil).to(block)
132
+ end
133
+ end
134
+
135
+ end
data/spec/spec_helper.rb CHANGED
@@ -6,10 +6,13 @@ require 'bundler/setup'
6
6
  Bundler.setup
7
7
 
8
8
  require 'pry'
9
+ require 'helpers/benchmark_helper'
9
10
  require 'cloud_sesame'
10
11
 
11
12
  RSpec.configure do |config|
12
13
  # For running just wanted tests in guard
13
14
  config.filter_run :focus => true
14
15
  config.run_all_when_everything_filtered = true
16
+
17
+ config.extend BenchmarkHelper
15
18
  end