CloudSesame 0.6.8 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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