neuron-client 0.2.6 → 0.3.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 (116) hide show
  1. data/README.md +11 -8
  2. data/lib/neuron-client/api.rb +5 -0
  3. data/lib/neuron-client/model/ad.rb +142 -0
  4. data/lib/neuron-client/model/ad_calculations.rb +325 -0
  5. data/lib/neuron-client/model/ad_zone.rb +45 -0
  6. data/lib/neuron-client/model/base.rb +233 -18
  7. data/lib/neuron-client/model/blocked_referer.rb +20 -0
  8. data/lib/neuron-client/model/blocked_user_agent.rb +21 -0
  9. data/lib/neuron-client/model/geo_target.rb +36 -0
  10. data/lib/neuron-client/model/pixel.rb +22 -0
  11. data/lib/neuron-client/model/report.rb +35 -0
  12. data/lib/neuron-client/model/s3_file.rb +30 -0
  13. data/lib/neuron-client/model/zone.rb +64 -0
  14. data/lib/neuron-client/model/zone_calculations.rb +37 -0
  15. data/lib/neuron-client/schema/ad.rb +406 -0
  16. data/lib/neuron-client/schema/ad_zone.rb +49 -0
  17. data/lib/neuron-client/schema/blocked_referer.rb +52 -0
  18. data/lib/neuron-client/schema/blocked_user_agent.rb +64 -0
  19. data/lib/neuron-client/schema/common.rb +220 -0
  20. data/lib/neuron-client/schema/event.rb +17 -0
  21. data/lib/neuron-client/schema/geo_target.rb +33 -0
  22. data/lib/neuron-client/schema/pixel.rb +39 -0
  23. data/lib/neuron-client/schema/report.rb +59 -0
  24. data/lib/neuron-client/schema/s3_file.rb +87 -0
  25. data/lib/neuron-client/schema/zone.rb +214 -0
  26. data/lib/neuron-client/version.rb +1 -1
  27. data/lib/neuron-client.rb +24 -59
  28. data/neuron-client.gemspec +3 -0
  29. data/spec/lib/admin_connection_spec.rb +234 -0
  30. data/spec/lib/api_spec.rb +41 -63
  31. data/spec/lib/model/ad_calculations_spec.rb +1146 -0
  32. data/spec/lib/model/ad_spec.rb +253 -0
  33. data/spec/lib/model/ad_zone_spec.rb +15 -0
  34. data/spec/lib/model/base_spec.rb +5 -83
  35. data/spec/lib/model/blocked_referer_spec.rb +36 -0
  36. data/spec/lib/model/blocked_user_agent_spec.rb +36 -0
  37. data/spec/lib/model/geo_target_spec.rb +28 -0
  38. data/spec/lib/model/pixel_spec.rb +36 -0
  39. data/spec/lib/model/report_spec.rb +17 -0
  40. data/spec/lib/{s3_file_spec.rb → model/s3_file_spec.rb} +6 -5
  41. data/spec/lib/model/zone_calculations_spec.rb +49 -0
  42. data/spec/lib/model/zone_spec.rb +155 -0
  43. data/spec/lib/schema/ad_spec.rb +515 -0
  44. data/spec/lib/schema/ad_zone_spec.rb +149 -0
  45. data/spec/lib/schema/blocked_referer_spec.rb +136 -0
  46. data/spec/lib/schema/blocked_user_agent_spec.rb +147 -0
  47. data/spec/lib/schema/geo_target_spec.rb +92 -0
  48. data/spec/lib/schema/pixel_spec.rb +125 -0
  49. data/spec/lib/schema/report_spec.rb +129 -0
  50. data/spec/lib/schema/s3_file_spec.rb +164 -0
  51. data/spec/lib/schema/zone_spec.rb +243 -0
  52. data/spec/spec_helper.rb +2 -1
  53. metadata +141 -121
  54. data/lib/neuron-client/model/admin/ad.rb +0 -22
  55. data/lib/neuron-client/model/admin/ad_zone.rb +0 -15
  56. data/lib/neuron-client/model/admin/base.rb +0 -91
  57. data/lib/neuron-client/model/admin/blocked_referer.rb +0 -12
  58. data/lib/neuron-client/model/admin/blocked_user_agent.rb +0 -12
  59. data/lib/neuron-client/model/admin/geo_target.rb +0 -16
  60. data/lib/neuron-client/model/admin/pixel.rb +0 -12
  61. data/lib/neuron-client/model/admin/report.rb +0 -15
  62. data/lib/neuron-client/model/admin/s3_file.rb +0 -12
  63. data/lib/neuron-client/model/admin/zone.rb +0 -15
  64. data/lib/neuron-client/model/common/ad.rb +0 -42
  65. data/lib/neuron-client/model/common/ad_calculations.rb +0 -329
  66. data/lib/neuron-client/model/common/ad_zone.rb +0 -17
  67. data/lib/neuron-client/model/common/base.rb +0 -67
  68. data/lib/neuron-client/model/common/blocked_referer.rb +0 -16
  69. data/lib/neuron-client/model/common/blocked_user_agent.rb +0 -16
  70. data/lib/neuron-client/model/common/geo_target.rb +0 -16
  71. data/lib/neuron-client/model/common/pixel.rb +0 -18
  72. data/lib/neuron-client/model/common/report.rb +0 -21
  73. data/lib/neuron-client/model/common/s3_file.rb +0 -16
  74. data/lib/neuron-client/model/common/zone.rb +0 -22
  75. data/lib/neuron-client/model/common/zone_calculations.rb +0 -41
  76. data/lib/neuron-client/model/membase/ad.rb +0 -49
  77. data/lib/neuron-client/model/membase/ad_zone.rb +0 -11
  78. data/lib/neuron-client/model/membase/blocked_referer.rb +0 -20
  79. data/lib/neuron-client/model/membase/blocked_user_agent.rb +0 -20
  80. data/lib/neuron-client/model/membase/geo_target.rb +0 -11
  81. data/lib/neuron-client/model/membase/pixel.rb +0 -22
  82. data/lib/neuron-client/model/membase/report.rb +0 -11
  83. data/lib/neuron-client/model/membase/s3_file.rb +0 -11
  84. data/lib/neuron-client/model/membase/zone.rb +0 -30
  85. data/lib/neuron-client/model/models.rb +0 -15
  86. data/spec/lib/model/admin/ad_spec.rb +0 -34
  87. data/spec/lib/model/admin/ad_zone_spec.rb +0 -19
  88. data/spec/lib/model/admin/base_spec.rb +0 -11
  89. data/spec/lib/model/admin/blocked_referer_spec.rb +0 -11
  90. data/spec/lib/model/admin/blocked_user_agent_spec.rb +0 -11
  91. data/spec/lib/model/admin/geo_target_spec.rb +0 -30
  92. data/spec/lib/model/admin/report_spec.rb +0 -21
  93. data/spec/lib/model/admin/s3_spec.rb +0 -11
  94. data/spec/lib/model/admin/zone_spec.rb +0 -21
  95. data/spec/lib/model/common/ad_calculations_spec.rb +0 -1151
  96. data/spec/lib/model/common/ad_spec.rb +0 -11
  97. data/spec/lib/model/common/ad_zone_spec.rb +0 -11
  98. data/spec/lib/model/common/base_spec.rb +0 -11
  99. data/spec/lib/model/common/blocked_referer_spec.rb +0 -11
  100. data/spec/lib/model/common/blocked_user_agent_spec.rb +0 -11
  101. data/spec/lib/model/common/geo_target_spec.rb +0 -11
  102. data/spec/lib/model/common/report_spec.rb +0 -11
  103. data/spec/lib/model/common/s3_spec.rb +0 -11
  104. data/spec/lib/model/common/zone_calculations_spec.rb +0 -54
  105. data/spec/lib/model/common/zone_spec.rb +0 -11
  106. data/spec/lib/model/membase/ad_spec.rb +0 -54
  107. data/spec/lib/model/membase/ad_zone_spec.rb +0 -11
  108. data/spec/lib/model/membase/base_spec.rb +0 -11
  109. data/spec/lib/model/membase/blocked_referer_spec.rb +0 -34
  110. data/spec/lib/model/membase/blocked_user_agent_spec.rb +0 -34
  111. data/spec/lib/model/membase/geo_target_spec.rb +0 -11
  112. data/spec/lib/model/membase/pixel_spec.rb +0 -34
  113. data/spec/lib/model/membase/report_spec.rb +0 -11
  114. data/spec/lib/model/membase/s3_spec.rb +0 -11
  115. data/spec/lib/model/membase/zone_spec.rb +0 -32
  116. data/spec/lib/old_spec.rb +0 -437
@@ -1,37 +1,252 @@
1
1
  module Neuron
2
2
  module Client
3
- module Model
4
- class Base
5
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id|^class$)/ }
3
+ module Base
6
4
 
7
- def initialize(attrs=nil)
8
- @proxied_model = self.class.class_to_proxy.new(attrs)
5
+ module ClassAndInstanceMethods
6
+
7
+ def find(id)
8
+ if connected_to_membase?
9
+ key = cache_key(id)
10
+ connection.local_cache.fetch(key) do
11
+ cached_json = connection.get(key)
12
+ if cached_json.present?
13
+ data = Yajl.load(cached_json)
14
+ validate_against_schema!(:show, data)
15
+ from_hash(data)
16
+ end
17
+ end
18
+ elsif connected_to_admin?
19
+ data = connection.get("#{resources_name}/#{id}")
20
+ validate_against_schema!(:show, data)
21
+ from_hash(data)
22
+ else
23
+ raise "Not configured!"
24
+ end
25
+ end
26
+
27
+ def all
28
+ if connected_to_membase?
29
+ key = cache_key(":all")
30
+ connection.local_cache.fetch(key) do
31
+ cached_json = connection.get(resources_name)
32
+ if cached_json.present?
33
+ data = Yajl.load(cached_json)
34
+ validate_against_schema!(:index, data)
35
+ data.map{ |hash| from_hash(hash) }
36
+ else
37
+ []
38
+ end
39
+ end
40
+ elsif connected_to_admin?
41
+ data = connection.get(resources_name)
42
+ validate_against_schema!(:index, data)
43
+ data.map{ |hash| from_hash(hash) }
44
+ else
45
+ raise "Not configured!"
46
+ end
47
+ end
48
+
49
+ def create(attrs={})
50
+ @errors = catch :errors do
51
+ return create!(attrs)
52
+ end
53
+ nil
54
+ end
55
+
56
+ def create!(attrs={})
57
+ data = {resource_name => attrs.stringify_keys}
58
+ validate_against_schema!(:create, data)
59
+ data = connection.post("#{resources_name}", data)
60
+ validate_against_schema!(:show, data)
61
+ from_hash(data)
9
62
  end
10
63
 
11
- def method_missing(meth, *args, &block)
12
- (@proxied_model.respond_to?(meth) ? @proxied_model.send(meth, *args, &block) : super)
64
+ def api=(api)
65
+ @api = api
66
+ end
67
+
68
+ def validate=(validate)
69
+ @validate = validate
70
+ end
71
+
72
+ protected
73
+
74
+ ID_PATTERN = /\A\d+\z/.freeze
75
+ def validate_id!(id)
76
+ if validate?
77
+ unless (id.is_a?(Integer) || ID_PATTERN.match(id.to_s)) && id.to_i > 0
78
+ raise "Invalid ID: #{id.inspect}"
79
+ end
80
+ end
81
+ end
82
+
83
+ UUID_PATTERN = /\A[a-z0-9]+\z/.freeze
84
+ def validate_uuid!(uuid)
85
+ if validate?
86
+ unless UUID_PATTERN.match(uuid) && uuid.length <= 25
87
+ raise "Invalid UUID: #{uuid}"
88
+ end
89
+ end
90
+ rescue Exception => e
91
+ raise "Invalid UUID: #{uuid}"
13
92
  end
14
93
 
15
- class << self
16
- attr_accessor :api
17
- def api
94
+ # schema_name : one of [:index, :show, :create, update]
95
+ # data : the serializable object that should match the given schema
96
+ def validate_against_schema!(schema_name, data)
97
+ if validate? && data.present?
98
+ JSON::Validator.validate!(schema.send(schema_name), data)
99
+ end
100
+ end
101
+
102
+ def from_hash(hash)
103
+ if hash.present?
104
+ attrs = hash[resource_name]
105
+ klass.new(attrs) if attrs.present?
106
+ end
107
+ end
108
+
109
+ def api
110
+ if self.is_a?(Class)
18
111
  @api || Neuron::Client::API.default_api
112
+ else
113
+ @api || self.class.api
114
+ end
115
+ end
116
+
117
+ def validate?
118
+ return !!@validate unless @validate.nil?
119
+ if self.is_a?(Class)
120
+ api.validate?
121
+ else
122
+ self.class.validate?
19
123
  end
124
+ end
125
+
126
+ def connection
127
+ api.connection
128
+ end
129
+
130
+ def connected?
131
+ connection.present?
132
+ end
133
+
134
+ def connected_to_admin?
135
+ api.connection_type == :admin && connected?
136
+ end
137
+
138
+ def connected_to_membase?
139
+ api.connection_type == :membase && connected?
140
+ end
20
141
 
21
- def connection
22
- api.connection
142
+ def connected_to_admin!
143
+ raise("Not configured for admin server!") unless connected_to_admin?
144
+ end
145
+
146
+ def connected_to_membase!
147
+ raise("Not configured for membase server!") unless connected_to_membase?
148
+ end
149
+
150
+ def klass
151
+ @klass ||= (self.is_a?(Class) ? self : self.class)
152
+ end
153
+
154
+ def schema
155
+ @schema ||= "Neuron::Schema::#{klass.name.demodulize}::SCHEMA".constantize
156
+ end
157
+
158
+ def cache_key(id)
159
+ "#{klass.name.demodulize}:#{id}"
160
+ end
161
+
162
+ def resource_name
163
+ @resource_name ||= klass.name.demodulize.underscore
164
+ end
165
+
166
+ def resources_name
167
+ @resources_name ||= resource_name.pluralize
168
+ end
169
+ end
170
+
171
+ def self.included(base)
172
+ base.send(:attr_accessor, :errors) if base.is_a?(Class)
173
+ base.send(:attr_reader, :id) if base.is_a?(Class)
174
+ base.extend(ClassAndInstanceMethods)
175
+ end
176
+
177
+ include ClassAndInstanceMethods
178
+
179
+ def initialize(attrs={})
180
+ apply_attributes!(attrs)
181
+ end
182
+
183
+ def apply_attributes!(attrs)
184
+ if attrs.present?
185
+ attrs.each do |k,v|
186
+ self.send("#{k}=", v) if self.respond_to?("#{k}=")
23
187
  end
188
+ end
189
+ end
24
190
 
25
- def class_to_proxy
26
- module_to_load = api.connection_type.to_s.titleize
27
- class_name_to_load = name.split('::').last
28
- Neuron::Client::Model.const_get(module_to_load).const_get(class_name_to_load)
191
+ def to_hash(*except)
192
+ hash = {}
193
+ except = except.map(&:to_sym)
194
+ attributes.each do |attribute|
195
+ unless except.include?(attribute.to_sym)
196
+ value = send(attribute)
197
+ hash[attribute.to_s] = value
29
198
  end
199
+ end
200
+ {resource_name => hash}
201
+ end
202
+
203
+ def to_create_hash(*except)
204
+ to_hash(*([:errors, :updated_at, :created_at, :id] + except))
205
+ end
206
+
207
+ def to_update_hash(*except)
208
+ to_hash(*([:errors, :updated_at, :created_at] + except))
209
+ end
210
+
211
+ def new_record?
212
+ id.nil?
213
+ end
214
+
215
+ def id=(id)
216
+ @id = Integer(id)
217
+ end
218
+
219
+ def save
220
+ update_attributes
221
+ end
30
222
 
31
- def method_missing(meth, *args, &block)
32
- (class_to_proxy.respond_to?(meth) ? class_to_proxy.send(meth, *args, &block) : super)
223
+ def update_attributes(attrs={})
224
+ connected_to_admin!
225
+ @errors = catch :errors do
226
+ apply_attributes!(attrs) if attrs.present?
227
+ data = {}
228
+ if new_record?
229
+ data = to_create_hash
230
+ validate_against_schema!(:create, data)
231
+ data = connection.post(resources_name, data)
232
+ else
233
+ data = to_update_hash
234
+ validate_against_schema!(:update, data)
235
+ data = connection.put("#{resources_name}/#{id}", data)
33
236
  end
237
+ apply_attributes!(data[resource_name])
238
+ []
34
239
  end
240
+ @errors.empty?
241
+ end
242
+
243
+ def valid?
244
+ @errors.empty?
245
+ end
246
+
247
+ def destroy
248
+ connected_to_admin!
249
+ connection.delete("#{resources_name}/#{id}")
35
250
  end
36
251
  end
37
252
  end
@@ -0,0 +1,20 @@
1
+ module Neuron
2
+ module Client
3
+ class BlockedReferer
4
+ include Base
5
+
6
+ ATTRIBUTES = [
7
+ :id,
8
+ :referer, # string, URL
9
+ :created_at, # string, datetime in UTC
10
+ :updated_at, # string, datetime in UTC
11
+ ]
12
+
13
+ attr_accessor *ATTRIBUTES
14
+
15
+ def attributes
16
+ ATTRIBUTES
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module Neuron
2
+ module Client
3
+ class BlockedUserAgent
4
+ include Base
5
+
6
+ ATTRIBUTES = [
7
+ :id,
8
+ :user_agent, # string, regex (max 2 GB length)
9
+ :description, # nil, or string (max 2 GB length)
10
+ :created_at, # string, datetime in UTC
11
+ :updated_at, # string, datetime in UTC
12
+ ]
13
+
14
+ attr_accessor *ATTRIBUTES
15
+
16
+ def attributes
17
+ ATTRIBUTES
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ module Neuron
2
+ module Client
3
+ class GeoTarget
4
+ include Base
5
+
6
+ TYPES = %w(COUNTRY REGION METRO CITY) # DO NOT modify, unless you're absolutely sure of all the ramifications!
7
+
8
+ ATTRIBUTES = [
9
+ :id,
10
+ :geo_type, # string, one of TYPES
11
+ :net_acuity_id, # integer
12
+ :abbreviation, # string
13
+ :full_name, # string
14
+ :name, # string
15
+ ]
16
+
17
+ attr_accessor *ATTRIBUTES
18
+
19
+ def attributes
20
+ ATTRIBUTES
21
+ end
22
+
23
+ EXPECTED_QUERY_PARAMS = %w(geo_type search limit)
24
+ def self.query(parameters)
25
+ if validate?
26
+ unless parameters.all?{|k,v| EXPECTED_QUERY_PARAMS.include?(k.to_s)}
27
+ raise "Unsupported parameters: #{parameters.inspect}"
28
+ end
29
+ end
30
+ data = self.connection.get("geo_targets", parameters)
31
+ validate_against_schema!(:index, data)
32
+ data.map{ |hash| from_hash(hash) }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ module Neuron
2
+ module Client
3
+ class Pixel
4
+ include Base
5
+
6
+ ATTRIBUTES = [
7
+ :id,
8
+ :ad_ids, # array of integers
9
+ ]
10
+
11
+ attr_accessor *ATTRIBUTES
12
+
13
+ def attributes
14
+ ATTRIBUTES
15
+ end
16
+
17
+ def ad_ids
18
+ @ad_ids.nil? ? [] : @ad_ids
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ module Neuron
2
+ module Client
3
+ class Report
4
+ include Base
5
+
6
+ TEMPLATES = %w(ad_events delivery_metrics post_activities)
7
+
8
+ STATES = %w(WAITING RUNNING READY FAILED CANCELLED)
9
+
10
+ ATTRIBUTES = [
11
+ :id,
12
+ :parameters, # hash, where keys are parameter names, and values are parameter values as strings.
13
+ :state, # string, one of STATES
14
+ :template, # string, one of TEMPLATES
15
+ :created_at, # string, datetime in UTC
16
+ :updated_at, # string, datetime in UTC
17
+ ]
18
+
19
+ attr_accessor *ATTRIBUTES
20
+
21
+ def attributes
22
+ ATTRIBUTES
23
+ end
24
+
25
+ def status
26
+ @state
27
+ end
28
+
29
+ def result
30
+ connected_to_admin!
31
+ connection.get("reports/#{id}/result", :format => "")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ module Neuron
2
+ module Client
3
+ class S3File
4
+ include Base
5
+
6
+ PURPOSE_CHOICES = [
7
+ "REPORT_RESULT",
8
+ "RAW_EVENT_LOG",
9
+ "NORMALIZED_EVENT_LOG",
10
+ "ENRICHED_EVENT_LOG"
11
+ ]
12
+
13
+ ATTRIBUTES = [
14
+ :id,
15
+ :bucket, # string, at least 3 chars
16
+ :filename, # string
17
+ :filesize, # nil, or integer
18
+ :purpose, # string, one of PURPOSE_CHOICES
19
+ :created_at, # string, datetime in UTC
20
+ :updated_at, # string, datetime in UTC
21
+ ]
22
+
23
+ attr_accessor *ATTRIBUTES
24
+
25
+ def attributes
26
+ ATTRIBUTES
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,64 @@
1
+ module Neuron
2
+ module Client
3
+ class Zone
4
+ include Base
5
+ include ZoneCalculations
6
+
7
+ ADS_BY_PRIORITY_TTL = 60 #seconds
8
+
9
+ REDIRECT = "Redirect"
10
+ IRIS = "Iris"
11
+ VAST = "Vast"
12
+ RESPONSE_TYPES = [REDIRECT, IRIS, VAST]
13
+
14
+ TEMPLATE_SLUGS = ["300x250", "300x600"]
15
+
16
+ ATTRIBUTES = [
17
+ :id,
18
+ :ad_links,
19
+ :name,
20
+ :response_type, # string in RESPONSE_TYPES
21
+ :template_slug, # nil, or string in TEMPLATE_SLUGS
22
+ :mute, # nil, or "Yes" or "No"
23
+ :autoplay, # nil, or "Yes" or "No"
24
+ :channel, # nil, or slug
25
+ :expand, # nil, or "Yes" or "No"
26
+ :text_overlay, # nil, or "Yes" or "No"
27
+ :nami_feed_url, # nil, or string URL
28
+ :created_at, # string, datetime in UTC
29
+ :updated_at, # string, datetime in UTC
30
+ ]
31
+
32
+ attr_accessor *ATTRIBUTES
33
+
34
+ def attributes
35
+ ATTRIBUTES
36
+ end
37
+
38
+ def id=(id)
39
+ @id = id.to_s
40
+ end
41
+
42
+ def find_ad(ad_id)
43
+ Ad.find(ad_id)
44
+ end
45
+
46
+ def unlink(ad_id)
47
+ connected_to_admin!
48
+ validate_id!(ad_id)
49
+ validate_uuid!(id)
50
+ connection.delete("zones/#{id}/ads/#{ad_id}")
51
+ end
52
+
53
+ def ads_by_priority
54
+ if connected_to_membase?
55
+ connection.fetch("Zone:#{id}:ads_by_priority", ADS_BY_PRIORITY_TTL) do
56
+ calculate_ads_by_priority
57
+ end
58
+ else
59
+ calculate_ads_by_priority
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,37 @@
1
+ module Neuron
2
+ module Client
3
+ module ZoneCalculations
4
+ # This module expects the following methods to be defined:
5
+ #
6
+ # ad_links (Hash, keys are ad IDs, values are a sub-hash: {'priority' => p, 'weight' => w})
7
+ # find_ad(ad_id) (nil, or an object that responds to :active? and :pressure)
8
+
9
+ def ads_by_priority
10
+ calculate_ads_by_priority
11
+ end
12
+
13
+ def calculate_ads_by_priority
14
+ entries = {}
15
+ ad_links.each do |ad_id, link|
16
+ next unless ad = find_ad(ad_id)
17
+ next unless ad.active?
18
+ weight = link['weight'].to_f
19
+ priority = link['priority'].to_f
20
+ entries[priority] ||= []
21
+ entries[priority] << [ad_id, weighted_pressure(weight, ad.pressure)]
22
+ end
23
+ entries.sort_by do |priority, entry|
24
+ priority
25
+ end.map do |priority, entry|
26
+ entry.sort_by(&:first)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def weighted_pressure(weight, pressure)
33
+ [( [weight, 0.0].max * [pressure, 1.0].max ), 1.0].max.to_f
34
+ end
35
+ end
36
+ end
37
+ end