best_boy 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,25 +7,115 @@ module BestBoy
7
7
 
8
8
  layout 'best_boy_backend'
9
9
 
10
- helper_method :available_owner_types, :available_events, :available_event_sources, :available_years, :current_owner_type,
11
- :current_event, :current_event_source, :current_year, :collection, :statistics, :stats_by_event_and_month,
12
- :stats_by_event_source_and_month, :render_chart, :event_source_details, :month_name_array, :detail_count,
13
- :current_month, :stats_by_event_source_and_day
10
+ helper_method :available_owner_types, :available_events, :available_event_sources, :available_years,
11
+ :current_owner_type, :current_event, :current_event_source, :current_month, :current_year, :collection,
12
+ :render_chart, :month_name_array, :detail_count
13
+
14
+
15
+ def stats
16
+ counter_scope = BestBoyEvent.select("COUNT(*) as counter, event").where(:owner_type => current_owner_type).group('event')
17
+
18
+ # Custom hash for current event stats - current_year, current_month, current_week, current_day (with given current_owner_type)
19
+ # We fire 5 database queries, one for each group, to keep it database acnostic.
20
+ # Before we had 5 * n events queries
21
+ @event_counts_per_group = {}
22
+ overall_hash = counter_scope.inject({}){ |hash, element| hash[element.event] = element.counter; hash}
23
+ current_year_hash = counter_scope.per_year(Time.zone.now).inject({}){ |hash, element| hash[element.event] = element.counter; hash }
24
+ current_month_hash = counter_scope.per_month(Time.zone.now).inject({}){ |hash, element| hash[element.event] = element.counter; hash }
25
+ current_week_hash = counter_scope.per_week(Time.zone.now).inject({}){ |hash, element| hash[element.event] = element.counter; hash }
26
+ current_day_hash = counter_scope.per_day(Time.zone.now).inject({}){ |hash, element| hash[element.event] = element.counter; hash }
27
+
28
+ available_events.each do |event|
29
+ @event_counts_per_group[event] ||= {}
30
+ @event_counts_per_group[event]['overall'] = overall_hash[event] || 0
31
+ @event_counts_per_group[event]['year'] = current_year_hash[event] || 0
32
+ @event_counts_per_group[event]['month'] = current_month_hash[event] || 0
33
+ @event_counts_per_group[event]['week'] = current_week_hash[event] || 0
34
+ @event_counts_per_group[event]['day'] = current_day_hash[event] || 0
35
+ end
36
+
37
+ # Custom hash for current event stats per month (with given current_owner_type)
38
+ # We fire 12 database queries, one for each month, to keep it database acnostic.
39
+ # Before we had 12 * n events queries
40
+ @event_counts_per_month = {}
41
+ %w(1 2 3 4 5 6 7 8 9 10 11 12).each do |month|
42
+ month_hash = counter_scope.per_month("1-#{month}-#{current_year}".to_time).inject({}){ |hash, element| hash[element.event] = element.counter; hash}
43
+
44
+ available_events.each do |event|
45
+ @event_counts_per_month[event] ||= {}
46
+ @event_counts_per_month[event][month] = month_hash[event] || 0
47
+ end
48
+ end
49
+ end
50
+
51
+
52
+ def details
53
+ counter_scope = BestBoyEvent.select("COUNT(*) as counter, event_source").where(owner_type: current_owner_type, event: current_event).group('event_source')
54
+
55
+ # Custom hash for current event_source stats - current_year, current_month, current_week, current_day (with given current_owner_type)
56
+ # We fire 5 database queries, one for each group, to keep it database acnostic.
57
+ # Before we had 5 * n event_sources queries
58
+ @event_source_counts_per_group = {}
59
+ overall_hash = counter_scope.inject({}){ |hash, element| hash[element.event_source] = element.counter; hash}
60
+ current_year_hash = counter_scope.per_year(Time.zone.now).inject({}){ |hash, element| hash[element.event_source] = element.counter; hash}
61
+ current_month_hash = counter_scope.per_month(Time.zone.now).inject({}){ |hash, element| hash[element.event_source] = element.counter; hash}
62
+ current_week_hash = counter_scope.per_week(Time.zone.now).inject({}){ |hash, element| hash[element.event_source] = element.counter; hash}
63
+ current_day_hash = counter_scope.per_day(Time.zone.now).inject({}){ |hash, element| hash[element.event_source] = element.counter; hash}
64
+
65
+ available_event_sources.each do |event_source|
66
+ @event_source_counts_per_group[event_source] ||= {}
67
+ @event_source_counts_per_group[event_source]['overall'] = overall_hash[event_source] || 0
68
+ @event_source_counts_per_group[event_source]['year'] = current_year_hash[event_source] || 0
69
+ @event_source_counts_per_group[event_source]['month'] = current_month_hash[event_source] || 0
70
+ @event_source_counts_per_group[event_source]['week'] = current_week_hash[event_source] || 0
71
+ @event_source_counts_per_group[event_source]['day'] = current_day_hash[event_source] || 0
72
+ end
73
+
74
+ # Custom hash for current event_sources stats per month (with given current_owner_type and given event)
75
+ # We fire 12 database queries, one for each month, to keep it database acnostic.
76
+ # Before we had 12 * n event_sources queries
77
+ @event_sources_counts_per_month = {}
78
+ %w(1 2 3 4 5 6 7 8 9 10 11 12).each do |month|
79
+ month_hash = counter_scope.per_month("1-#{month}-#{current_year}".to_time).inject({}){ |hash, element| hash[element.event_source] = element.counter; hash}
80
+
81
+ available_event_sources.each do |event_source|
82
+ @event_sources_counts_per_month[event_source] ||= {}
83
+ @event_sources_counts_per_month[event_source][month] = month_hash[event_source] || 0
84
+ end
85
+ end
86
+ end
87
+
14
88
 
15
89
  def monthly_details
90
+ counter_scope = BestBoyEvent.select("COUNT(*) as counter, event_source").where(owner_type: current_owner_type, event: current_event).group('event_source')
91
+ days = 1..(Time.days_in_month("1-#{current_month}-#{current_year}".to_time.month))
92
+ # Custom hash for current event_sources stats per month (with given current_owner_type and given event)
93
+ # We fire a max of 31 database queries, one for each day, to keep it database acnostic.
94
+ # Before we had 31 * n event_sources queries
95
+ @event_sources_counts_per_day = {}
96
+ days.each do |day|
97
+ day_hash = counter_scope.per_day("#{day}-#{current_month}-#{current_year}".to_time).inject({}){ |hash, element| hash[element.event_source] = element.counter; hash}
98
+
99
+ available_event_sources.each do |event_source|
100
+ @event_sources_counts_per_day[event_source] ||= {}
101
+ @event_sources_counts_per_day[event_source][day] = day_hash[event_source] || 0
102
+ end
103
+ end
104
+
16
105
  data_table = GoogleVisualr::DataTable.new
17
106
  data_table.new_column('string', 'time')
18
107
  available_event_sources.each do |source|
19
108
  data_table.new_column('number', source.to_s)
20
109
  end
21
110
 
22
- (1..(Time.days_in_month("1-#{current_month}-#{current_year}".to_time.month))).each do |periode|
23
- time = "#{periode}-#{current_month}-#{current_year}".to_time
24
- data_table.add_row( [ periode.to_s] + available_event_sources.map{ |source| custom_data_count(source, time)})
111
+ days.each do |day|
112
+ time = "#{day}-#{current_month}-#{current_year}".to_time
113
+ data_table.add_row( [ day.to_s] + available_event_sources.map{ |event_source| @event_sources_counts_per_day[event_source][day].to_i })
25
114
  end
26
115
  @chart = GoogleVisualr::Interactive::AreaChart.new(data_table, { width: 900, height: 240, title: "" })
27
116
  end
28
117
 
118
+
29
119
  private
30
120
 
31
121
  def render_chart(chart, dom)
@@ -51,9 +141,9 @@ module BestBoy
51
141
  end
52
142
 
53
143
  def custom_data_count(source, time)
54
- scope = BestBoyEvent.where("best_boy_events.owner_type = ?", current_owner_type)
55
- scope = scope.where("best_boy_events.event = ?", current_event) if current_event.present?
56
- scope = scope.where("best_boy_events.event_source = ?", source) if source.present?
144
+ scope = BestBoyEvent.where(owner_type: current_owner_type)
145
+ scope = scope.where(event: current_event) if current_event.present?
146
+ scope = scope.where(event_source: source) if source.present?
57
147
  scope = scope.send("per_#{ current_time_interval == "year" ? "month" : "day" }", time)
58
148
  scope.count
59
149
  end
@@ -91,31 +181,6 @@ module BestBoy
91
181
  end
92
182
  end
93
183
 
94
- def stats_by_event_and_month(event, month)
95
- date = "1-#{month}-#{current_year}".to_time
96
- BestBoyEvent.where("best_boy_events.owner_type = ? AND best_boy_events.event = ?", current_owner_type, event).per_month(date).count
97
- end
98
-
99
- def stats_by_owner_and_event_and_event_source(source)
100
- if source.present?
101
- scope = BestBoyEvent.where("best_boy_events.owner_type = ? AND best_boy_events.event = ? AND best_boy_events.event_source = ?", current_owner_type, current_event, source)
102
- else
103
- scope = BestBoyEvent.where("best_boy_events.owner_type = ? AND best_boy_events.event = ? AND best_boy_events.event_source IS NULL", current_owner_type, current_event)
104
- end
105
- end
106
-
107
- def stats_by_event_source_and_month(source, month)
108
- date = "1-#{month}-#{current_year}".to_time
109
- scope = stats_by_owner_and_event_and_event_source(source)
110
- scope.per_month(date).count
111
- end
112
-
113
- def stats_by_event_source_and_day(source, day)
114
- date = "#{day}-#{current_month}-#{current_year}".to_time
115
- scope = stats_by_owner_and_event_and_event_source(source)
116
- scope.per_day(date).count
117
- end
118
-
119
184
  def current_date
120
185
  @current_date ||= Date.strptime(params[:date], "%d-%m-%Y") if params[:date] rescue nil
121
186
  end
@@ -145,27 +210,21 @@ module BestBoy
145
210
  end
146
211
 
147
212
  def available_events
148
- @available_events ||= (
149
- scope = BestBoyEvent.where("best_boy_events.owner_type = ?", current_owner_type)
150
- scope = scope.select("best_boy_events.event").order("best_boy_events.event ASC")
151
- scope = scope.group("best_boy_events.event").map(&:event)
152
- )
213
+ @available_events ||= BestBoyEvent.select('DISTINCT event').where(owner_type: current_owner_type).order(:event).map(&:event)
153
214
  end
154
215
 
155
216
  def available_event_sources
156
217
  @available_event_sources ||= (
157
- scope = BestBoyEvent.where("best_boy_events.owner_type = ? AND best_boy_events.event = ?", current_owner_type, current_event)
158
- scope = scope.select("best_boy_events.event_source").order("best_boy_events.event_source ASC")
159
- scope = scope.group("best_boy_events.event_source").map(&:event_source)
218
+ BestBoyEvent.select("DISTINCT event_source").where(owner_type: current_owner_type, event: current_event).order(:event_source).map(&:event_source)
160
219
  )
161
220
  end
162
221
 
163
222
  def available_years
164
- @available_years = (BestBoyEvent.where("best_boy_events.owner_type = ?", current_owner_type).order("best_boy_events.created_at ASC").first.created_at.to_date.year..Time.zone.now.year).map{ |year| year.to_s } rescue [Time.zone.now.year]
223
+ @available_years = (BestBoyEvent.where(owner_type: current_owner_type).order(:created_at).first.created_at.to_date.year..Time.zone.now.year).map{ |year| year.to_s } rescue [Time.zone.now.year]
165
224
  end
166
225
 
167
226
  def available_owner_types
168
- @available_owner_types ||= BestBoyEvent.select("DISTINCT best_boy_events.owner_type").order("best_boy_events.owner_type ASC").map(&:owner_type)
227
+ @available_owner_types ||= BestBoyEvent.select("DISTINCT owner_type").order(:owner_type).map(&:owner_type)
169
228
  end
170
229
 
171
230
  def detail_count
@@ -179,37 +238,19 @@ module BestBoy
179
238
  end
180
239
 
181
240
  scope = BestBoyEvent
182
- scope = scope.where("best_boy_events.owner_type = ?", @owner_type) if @owner_type.present?
183
- scope = scope.where("best_boy_events.event = ?", @event) if @event.present?
184
- scope = scope.where("best_boy_events.event_source = ?", @event_source) if @event_source.present?
241
+ scope = scope.where(owner_type: @owner_type) if @owner_type.present?
242
+ scope = scope.where(event: @event) if @event.present?
243
+ scope = scope.where(event_source: @event_source) if @event_source.present?
185
244
  scope = scope.per_day(@date) if @date.present?
186
245
  scope
187
246
  end
188
247
 
189
- def prepare_details(base_collection, key, options = {})
190
- array = Array.new
191
- base_collection.each do |item|
192
- scope = current_scope(options.to_a + [[key.to_sym, item]])
193
- array.push([item, scope.count] + %w(year month week day).map{ |delimiter| scope.send("per_#{delimiter}", Time.zone.now).count })
194
- end
195
- array
196
- end
197
-
198
248
  def collection
199
249
  @best_boy_events ||= (
200
- scope = current_scope({:owner_type => params[:owner_type], :event_source => current_event, :date => current_date})
201
- scope = scope.order("best_boy_events.created_at DESC, best_boy_events.event ASC")
202
- scope.page(params[:page]).per(50)
250
+ scope = current_scope({owner_type: params[:owner_type], event_source: current_event, date: current_date})
251
+ scope.order("created_at DESC, event ASC").page(params[:page]).per(50)
203
252
  )
204
253
  end
205
254
 
206
- def statistics
207
- @statistics = prepare_details(available_events, "event", {:owner_type => current_owner_type})
208
- end
209
-
210
- def event_source_details
211
- @event_source_details = prepare_details(available_event_sources, "event_source", {:owner_type => current_owner_type, :event => current_event})
212
- end
213
-
214
255
  end
215
256
  end
@@ -19,17 +19,31 @@
19
19
  </tr>
20
20
  </thead>
21
21
  <tbody>
22
- <% event_source_details.each do |best_boy_event_source| %>
22
+ <% @event_source_counts_per_group.each do |event_source_string, group_hash| %>
23
23
  <tr>
24
- <% (0..5).each do |key| %>
25
- <td><%= best_boy_event_source[key].present? ? best_boy_event_source[key] : "n.a." %></td>
26
- <% if key != 0 %>
27
- <td><%= number_with_precision((best_boy_event_source[key].to_f / detail_count) * 100, :precision => 2) %> %</td>
28
- <% end %>
24
+ <td><%= event_source_string %></td>
25
+ <% group_hash.each do |group, counter| %>
26
+ <td><%= counter %></td>
27
+ <td><%= number_to_percentage(counter.to_f / detail_count * 100, :precision => 2) %></td>
29
28
  <% end %>
30
29
  </tr>
31
30
  <% end %>
32
31
  </tbody>
32
+ <tfoot>
33
+ <tr>
34
+ <th>Total</th>
35
+ <th><%= result = @event_source_counts_per_group.values.inject(0){|sum, hash| sum += hash['overall'].to_i; sum} %></th>
36
+ <th><%= number_to_percentage(result.to_f / detail_count * 100, :precision => 2) %></th>
37
+ <th><%= result = @event_source_counts_per_group.values.inject(0){|sum, hash| sum += hash['year'].to_i; sum} %></th>
38
+ <th><%= number_to_percentage(result.to_f / detail_count * 100, :precision => 2) %></th>
39
+ <th><%= result = @event_source_counts_per_group.values.inject(0){|sum, hash| sum += hash['month'].to_i; sum} %></th>
40
+ <th><%= number_to_percentage(result.to_f / detail_count * 100, :precision => 2) %></th>
41
+ <th><%= result = @event_source_counts_per_group.values.inject(0){|sum, hash| sum += hash['week'].to_i; sum} %></th>
42
+ <th><%= number_to_percentage(result.to_f / detail_count * 100, :precision => 2) %></th>
43
+ <th><%= result = @event_source_counts_per_group.values.inject(0){|sum, hash| sum += hash['day'].to_i; sum} %></th>
44
+ <th><%= number_to_percentage(result.to_f / detail_count * 100, :precision => 2) %></th>
45
+ </tr>
46
+ </tfoot>
33
47
  </table>
34
48
  </div>
35
49
  </div>
@@ -47,18 +61,18 @@
47
61
  <table class="table table-striped table-bordered">
48
62
  <thead>
49
63
  <tr>
50
- <th>Event</th>
64
+ <th>Event Source</th>
51
65
  <% month_name_array.each do |month| %>
52
66
  <th><%= link_to month, best_boy_admin_monthly_details_path(:owner_type => current_owner_type, :event => current_event, :month => month, :year => current_year, :time_interval => "month") %></th>
53
67
  <% end %>
54
68
  </tr>
55
69
  </thead>
56
70
  <tbody>
57
- <% available_event_sources.each do |source| %>
71
+ <% @event_sources_counts_per_month.each do |event_source_string, month_hash| %>
58
72
  <tr>
59
- <td><%= source.present? ? source : "n.a." %></td>
60
- <% %w(1 2 3 4 5 6 7 8 9 10 11 12).each do |month| %>
61
- <td><%= stats_by_event_source_and_month source, month %></td>
73
+ <td><%= event_source_string %></td>
74
+ <% month_hash.each do |month, counter| %>
75
+ <td><%= counter %></td>
62
76
  <% end %>
63
77
  </tr>
64
78
  <% end %>
@@ -23,18 +23,18 @@
23
23
  <table class="table table-striped table-bordered small-font">
24
24
  <thead>
25
25
  <tr>
26
- <th>Event</th>
26
+ <th>Event Source</th>
27
27
  <% (1..(Time.days_in_month("1-#{current_month}-#{current_year}".to_time.month))).each do |day| %>
28
28
  <td><%= day %></td>
29
29
  <% end %>
30
30
  </tr>
31
31
  </thead>
32
32
  <tbody>
33
- <% available_event_sources.each do |source| %>
33
+ <% @event_sources_counts_per_day.each do |event_source_string, day_hash| %>
34
34
  <tr>
35
- <td><%= source.present? ? source : "n.a." %></td>
36
- <% (1..(Time.days_in_month("1-#{current_month}-#{current_year}".to_time.month))).each do |day| %>
37
- <td><%= stats_by_event_source_and_day source, day %></td>
35
+ <td><%= event_source_string %></td>
36
+ <% day_hash.each do |day, counter| %>
37
+ <td><%= counter %></td>
38
38
  <% end %>
39
39
  </tr>
40
40
  <% end %>
@@ -15,22 +15,32 @@
15
15
  </tr>
16
16
  </thead>
17
17
  <tbody>
18
- <% statistics.each do |best_boy_event| %>
18
+ <% @event_counts_per_group.each do |event_string, group_hash| %>
19
19
  <tr>
20
- <td><%= link_to best_boy_event[0], best_boy_admin_details_path(:event => best_boy_event[0], :owner_type => current_owner_type) %>
21
- <% (1..5).each do |key| %>
22
- <td><%= best_boy_event[key] %></td>
20
+ <td><%= link_to event_string, best_boy_admin_details_path(:event => event_string, :owner_type => current_owner_type) %></td>
21
+ <% group_hash.each do |group, counter| %>
22
+ <td><%= counter %></td>
23
23
  <% end %>
24
24
  </tr>
25
25
  <% end %>
26
26
  </tbody>
27
+ <tfoot>
28
+ <tr>
29
+ <th>Total</th>
30
+ <th><%= @event_counts_per_group.values.inject(0){|sum, hash| sum += hash['overall'].to_i; sum} %></th>
31
+ <th><%= @event_counts_per_group.values.inject(0){|sum, hash| sum += hash['year'].to_i; sum} %></th>
32
+ <th><%= @event_counts_per_group.values.inject(0){|sum, hash| sum += hash['month'].to_i; sum} %></th>
33
+ <th><%= @event_counts_per_group.values.inject(0){|sum, hash| sum += hash['week'].to_i; sum} %></th>
34
+ <th><%= @event_counts_per_group.values.inject(0){|sum, hash| sum += hash['day'].to_i; sum} %></th>
35
+ </tr>
36
+ </tfoot>
27
37
  </table>
28
38
  </div>
29
39
  </div>
30
40
 
31
41
  <div class="span12">
32
42
  <div class="well">
33
- <h3 class="pull-left">Statistics for <%= current_year %> per month</h3>
43
+ <h3 class="pull-left">Statistics for <%= current_year %> per month</h3>
34
44
  <div class="pull-right">
35
45
  <%= form_tag best_boy_admin_stats_path, :method => :get do %>
36
46
  <%= hidden_field_tag :owner_type, current_owner_type %>
@@ -47,15 +57,23 @@
47
57
  </tr>
48
58
  </thead>
49
59
  <tbody>
50
- <% available_events.each do |event| %>
60
+ <% @event_counts_per_month.each do |event_string, month_hash| %>
51
61
  <tr>
52
- <td><%= event %></td>
53
- <% %w(1 2 3 4 5 6 7 8 9 10 11 12).each do |month| %>
54
- <td><%= stats_by_event_and_month event, month %></td>
62
+ <td><%= event_string %></td>
63
+ <% month_hash.each do |month, counter| %>
64
+ <td><%= counter %></td>
55
65
  <% end %>
56
66
  </tr>
57
67
  <% end %>
58
68
  </tbody>
69
+ <tfoot>
70
+ <tr>
71
+ <th>Total</th>
72
+ <% (1..12).to_a.map(&:to_s).each do |month_no| %>
73
+ <th><%= @event_counts_per_month.values.inject(0){|sum, hash| sum += hash[month_no].to_i; sum} %></th>
74
+ <% end %>
75
+ </tr>
76
+ </tfoot>
59
77
  </table>
60
78
  </div>
61
79
  </div>
@@ -19,8 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.add_dependency('kaminari')
20
20
  s.add_dependency('google_visualr')
21
21
 
22
- s.add_development_dependency('activerecord')
23
- s.add_development_dependency('activesupport')
22
+ s.add_development_dependency('activerecord', '~> 3.2.13')
23
+ s.add_development_dependency('activesupport', '~> 3.2.13')
24
24
  s.add_development_dependency('sqlite3', '~> 1.3.6')
25
25
  s.add_development_dependency('rake', '~> 10.0.3')
26
26
  s.add_development_dependency('rspec', '~> 2.13.0')
Binary file
@@ -1,3 +1,3 @@
1
1
  module BestBoy
2
- VERSION = "1.0.3"
2
+ VERSION = "1.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: best_boy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-07 00:00:00.000000000 Z
12
+ date: 2013-06-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: kaminari
@@ -48,33 +48,33 @@ dependencies:
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
- - - ! '>='
51
+ - - ~>
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: 3.2.13
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  none: false
58
58
  requirements:
59
- - - ! '>='
59
+ - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 3.2.13
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: activesupport
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
67
- - - ! '>='
67
+ - - ~>
68
68
  - !ruby/object:Gem::Version
69
- version: '0'
69
+ version: 3.2.13
70
70
  type: :development
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
- - - ! '>='
75
+ - - ~>
76
76
  - !ruby/object:Gem::Version
77
- version: '0'
77
+ version: 3.2.13
78
78
  - !ruby/object:Gem::Dependency
79
79
  name: sqlite3
80
80
  requirement: !ruby/object:Gem::Requirement