best_boy 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +35 -1
  4. data/app/controllers/best_boy/best_boy_events_controller.rb +65 -0
  5. data/app/views/best_boy/best_boy_events/_navigation.html.erb +9 -0
  6. data/app/views/best_boy/best_boy_events/index.html.erb +6 -0
  7. data/app/views/best_boy/best_boy_events/lists.html.erb +43 -0
  8. data/app/views/best_boy/best_boy_events/stats.html.erb +60 -0
  9. data/app/views/layouts/best_boy_backend.html.erb +62 -0
  10. data/best_boy.gemspec +9 -6
  11. data/config/routes.rb +5 -0
  12. data/db/bestboy.db +0 -0
  13. data/lib/best_boy.rb +5 -1
  14. data/{app → lib/best_boy}/controllers/best_boy_controller.rb +1 -1
  15. data/lib/best_boy/engine.rb +3 -1
  16. data/lib/best_boy/models/active_record/best_boy/eventable.rb +4 -0
  17. data/lib/best_boy/models/active_record/best_boy_event.rb +11 -1
  18. data/lib/best_boy/version.rb +1 -1
  19. data/lib/generators/best_boy_generator.rb +6 -0
  20. data/lib/generators/templates/best_boy.rb +15 -3
  21. data/lib/generators/templates/bootstrap/bootstrap.css +4983 -0
  22. data/lib/generators/templates/bootstrap/bootstrap_datepicker.css +156 -0
  23. data/lib/generators/templates/bootstrap/bootstrap_datepicker.js +401 -0
  24. data/lib/generators/templates/bootstrap/glyphicons-halflings-white.png +0 -0
  25. data/lib/generators/templates/bootstrap/glyphicons-halflings.png +0 -0
  26. data/log/test.log +1 -0
  27. data/spec/best_boy/best_boy_controller_spec.rb +4 -4
  28. data/spec/best_boy/best_boy_event_spec.rb +49 -3
  29. data/spec/best_boy/eventable_spec.rb +4 -4
  30. data/spec/spec_helper.rb +8 -8
  31. metadata +54 -20
  32. data/app/models/best_boy/eventable.rb +0 -10
data/.gitignore CHANGED
@@ -3,3 +3,4 @@
3
3
  .bundle
4
4
  Gemfile.lock
5
5
  pkg/*
6
+ /db
@@ -1,3 +1,9 @@
1
+ ## 0.1.0
2
+
3
+ * added backend
4
+ * added dependency kaminari for backend pagination
5
+ * extended specs
6
+
1
7
  ## 0.0.3
2
8
 
3
9
  * added rspec tests
data/README.md CHANGED
@@ -27,6 +27,20 @@ Generate the BestBoyEvent table migration
27
27
 
28
28
  rails g best_boy
29
29
 
30
+ This will install following files into your application
31
+
32
+ config/initializers/best_boy.rb
33
+
34
+ db_migrate/create_best_boy_events_table.rb
35
+
36
+ public/javascripts/bootstrap_datepicker.js
37
+
38
+ public/stylesheets/bootstrap.css
39
+
40
+ public/stylesheets/bootstrap_datepicker.css
41
+
42
+ See usage section for version information.
43
+
30
44
  Run the migration
31
45
 
32
46
  rake db:migrate
@@ -46,6 +60,7 @@ In controller context:
46
60
  best_boy_event object, event
47
61
 
48
62
  This will log custom events for a object and a event phrase.
63
+
49
64
  If no Object is given, it will raise an exception as well as if no event is provided.
50
65
 
51
66
 
@@ -61,7 +76,26 @@ BestBoyEvent table
61
76
  Getting BestBoyEvents
62
77
  ---------------------
63
78
 
64
- The table is namespaced, so you can access for statistics maybe with BestBoy::BestBoyEvent.where...
79
+ BestBoy comes with an admin interface, largely configurable to fit right into your existing application.
80
+
81
+ Following configurations can be done:
82
+
83
+ config.orm "symbole" # default: :active_record # for now only active_record is supported
84
+ config.base_controller "String" # default: "ApplicationController" # declare with Controller should be inherited
85
+ config.before_filter "comma separated symbols" # default: nil # declare before_filter to use inherited before_filters in admin section
86
+ config.skip_before_filter "comma separated symbols" # default: nil # declare skip_before_filter to skip inherited before_filters in admin section
87
+ config.custom_redirect "relative path as String" # default: nil # set a path to return back to your existing backend
88
+
89
+ After installation you can access it through:
90
+
91
+ <your application path>/best_boy_admin
92
+
93
+
94
+ Used gems and resource
95
+ ----------------------
96
+ [Twitter Bootstrap](http://twitter.github.com/bootstrap/) in its version 2.0.4
97
+
98
+ [Stefan Petre](http://www.eyecon.ro/bootstrap-datepicker) for Datepicker in Twitter Bootstrap style
65
99
 
66
100
  Thanks
67
101
  ------
@@ -0,0 +1,65 @@
1
+ module BestBoy
2
+ class BestBoyEventsController < BestBoy.base_controller.constantize
3
+
4
+ before_filter BestBoy.before_filter if BestBoy.before_filter.present?
5
+ skip_before_filter BestBoy.skip_before_filter if BestBoy.skip_before_filter.present?
6
+
7
+ layout 'best_boy_backend'
8
+
9
+ helper_method :available_owner_types, :available_events, :available_years, :current_owner_type, :current_event, :current_year, :collection, :statistics, :stats_by_event_and_month
10
+
11
+ private
12
+
13
+ def current_owner_type
14
+ @current_owner_type ||= available_owner_types.include?(params[:owner_type]) ? params[:owner_type] : available_owner_types.first
15
+ end
16
+
17
+ def current_event
18
+ @current_event ||= available_events.include?(params[:event]) ? params[:event] : nil
19
+ end
20
+
21
+ def current_date
22
+ @current_date ||= Date.strptime(params[:date], "%d-%m-%Y") if params[:date] rescue nil
23
+ end
24
+
25
+ def current_year
26
+ @current_year ||= available_years.include?(params[:year]) ? params[:year] : Time.zone.now.year
27
+ end
28
+
29
+ def available_owner_types
30
+ @available_owner_types ||= BestBoyEvent.select("DISTINCT best_boy_events.owner_type").order("best_boy_events.owner_type ASC").map(&:owner_type)
31
+ end
32
+
33
+ def available_events
34
+ @available_events ||= BestBoyEvent.where("best_boy_events.owner_type = ?", current_owner_type).select("best_boy_events.event").order("best_boy_events.event ASC").group("best_boy_events.event").map(&:event)
35
+ end
36
+
37
+ def available_years
38
+ @available_years = (BestBoyEvent.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]
39
+ end
40
+
41
+ def collection
42
+ @best_boy_events ||= (
43
+ scope = BestBoyEvent.where("best_boy_events.owner_type = ?", current_owner_type)
44
+ scope = scope.where("best_boy_events.event = ?", current_event) if current_event.present?
45
+ scope = scope.per_day(current_date) if current_date.present?
46
+ scope = scope.order("best_boy_events.created_at DESC, best_boy_events.event ASC")
47
+ scope.page(params[:page]).per(50)
48
+ )
49
+ end
50
+
51
+ def statistics
52
+ @statistics = Array.new
53
+ available_events.each do |event|
54
+ scope = BestBoyEvent.where("best_boy_events.owner_type = ? AND best_boy_events.event = ?", current_owner_type, event)
55
+ @statistics.push([event, scope.count] + %w(year month week day).map{ |delimiter| scope.send("per_#{delimiter}", Time.zone.now).count })
56
+ end
57
+ @statistics
58
+ end
59
+
60
+ def stats_by_event_and_month event, month
61
+ date = "1-#{month}-#{current_year}".to_time
62
+ BestBoyEvent.where("best_boy_events.owner_type = ? AND best_boy_events.event = ?", current_owner_type, event).per_month(date).count
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ <div class="span12">
2
+ <ul class="nav nav-tabs">
3
+ <% available_owner_types.each do |owner_type| %>
4
+ <li class="<%= (current_owner_type == owner_type) ? 'active' : nil %>">
5
+ <%= link_to owner_type, (referrer == "stats") ? best_boy_admin_stats_path(:owner_type => owner_type) : best_boy_admin_lists_path(:owner_type => owner_type) %>
6
+ </li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
@@ -0,0 +1,6 @@
1
+ <div class="span12">
2
+ <div class="hero-unit">
3
+ <h2>BestBoy Administation Center</h1>
4
+ <p>BestBoy provides several statistics and logs for you. You can access yearly, monthly, weekly and day statistics for your logged events per owner class. In the list and logs section you can see realtime events and search for date and events for each owner class.</p>
5
+ </div>
6
+ </div>
@@ -0,0 +1,43 @@
1
+ <%= render "navigation", :referrer => "lists" %>
2
+ <% content_for :stylesheets do %>
3
+ <%= stylesheet_link_tag "bootstrap_datepicker" %>
4
+ <% end %>
5
+ <% content_for :javascripts do %>
6
+ <%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" %>
7
+ <%= javascript_include_tag "bootstrap_datepicker" %>
8
+ <script type="text/javascript">
9
+ $(function(){$('#dp1').datepicker().on('changeDate', function(){$('#list-form').submit();})});
10
+ </script>
11
+ <% end %>
12
+
13
+ <div class="span12">
14
+ <div class="well">
15
+ <h3 class="pull-left">Log</h3>
16
+ <div class="pull-right">
17
+ <%= form_tag best_boy_admin_lists_path, :method => :get, :id => "list-form" do %>
18
+ <div class="input-append date pull-left span3" id="dp1" data-date="<%= params[:date].present? ? params[:date] : l(Time.zone.now, :format => "%d-%m-%Y") %>" data-date-format="dd-mm-yyyy">
19
+ <input name="date" class="span2" size="16" type="text" value="<%= params[:date] %>" readonly="readonly">
20
+ <span class="add-on"><i class="icon-th"></i></span>
21
+ </div>
22
+ <%= select_tag :event, options_for_select([["all Events", nil]] + available_events, :selected => current_event), :onchange => "submit();", :class => "span4" %>
23
+ <% end %>
24
+ </div>
25
+ <table class="table table-striped table-bordered">
26
+ <thead>
27
+ <tr>
28
+ <th>Event</th>
29
+ <th>created_at</th>
30
+ </tr>
31
+ </thead>
32
+ <tbody>
33
+ <% collection.each do |best_boy_event| %>
34
+ <tr>
35
+ <td><%= best_boy_event.event %></td>
36
+ <td><%= best_boy_event.created_at %></td>
37
+ </tr>
38
+ <% end %>
39
+ </tbody>
40
+ </table>
41
+ <%= paginate collection %>
42
+ </div>
43
+ </div>
@@ -0,0 +1,60 @@
1
+ <%= render "navigation", :referrer => "stats" %>
2
+
3
+ <div class="span12">
4
+ <div class="well">
5
+ <h3>Statistics</h3>
6
+ <table class="table table-striped table-bordered">
7
+ <thead>
8
+ <tr>
9
+ <th>Event</th>
10
+ <th>overall</th>
11
+ <th>year</th>
12
+ <th>month</th>
13
+ <th>week</th>
14
+ <th>day</th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ <% statistics.each do |best_boy_event| %>
19
+ <tr>
20
+ <% (0..5).each do |key| %>
21
+ <td><%= best_boy_event[key] %></td>
22
+ <% end %>
23
+ </tr>
24
+ <% end %>
25
+ </tbody>
26
+ </table>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="span12">
31
+ <div class="well">
32
+ <h3 class="pull-left">Statistics for <%= current_year %> per month</h3>
33
+
34
+ <div class="pull-right">
35
+ <%= form_tag best_boy_admin_stats_path, :method => :get do %>
36
+ <%= select_tag :year, options_for_select(available_years, :selected => current_year), :onchange => "submit();" %>
37
+ <% end %>
38
+ </div>
39
+ <table class="table table-striped table-bordered">
40
+ <thead>
41
+ <tr>
42
+ <th>Event</th>
43
+ <% %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec).each do |month| %>
44
+ <th><%= month %></th>
45
+ <% end %>
46
+ </tr>
47
+ </thead>
48
+ <tbody>
49
+ <% available_events.each do |event| %>
50
+ <tr>
51
+ <td><%= event %></td>
52
+ <% %w(1 2 3 4 5 6 7 8 9 10 11 12).each do |month| %>
53
+ <td><%= stats_by_event_and_month event, month %></td>
54
+ <% end %>
55
+ </tr>
56
+ <% end %>
57
+ </tbody>
58
+ </table>
59
+ </div>
60
+ </div>
@@ -0,0 +1,62 @@
1
+ <!doctype html>
2
+ <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
3
+ <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
4
+ <!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
5
+ <!-- Consider adding a manifest.appcache: h5bp.com/d/Offline -->
6
+ <!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
7
+ <head>
8
+ <meta charset="utf-8">
9
+ <meta content='IE=edge,chrome=1' http-equiv='X-UA-Compatible'>
10
+ <meta name="description" content="">
11
+ <meta name="viewport" content="width=device-width">
12
+ <title>BBAC - BestBoy Administration Center</title>
13
+ <%= csrf_meta_tag %>
14
+ <%= stylesheet_link_tag("bootstrap", :media => "all") %>
15
+ <%= yield :stylesheets %>
16
+ <%= yield :javascripts %>
17
+ <style>
18
+ /* FIX for kaminari gem */
19
+ nav.pagination {
20
+ text-align: center;
21
+ }
22
+ span.current,
23
+ span.gap a {
24
+ float: left;
25
+ padding: 0 9px;
26
+ line-height: 34px;
27
+ text-decoration: none;
28
+ border: 1px solid #DDD;
29
+ border-left-width: 0;
30
+ }
31
+ span.current {
32
+ border: 1px solid #DDD;
33
+ color: #999;
34
+ cursor: default;
35
+ background-color: whiteSmoke;
36
+ }
37
+ </style>
38
+ </head>
39
+ <body style="padding-top: 60px;">
40
+ <div class="navbar navbar-fixed-top">
41
+ <div class="navbar-inner">
42
+ <div class="container">
43
+ <%= link_to "BBAC", best_boy_admin_path, :class => "brand" %>
44
+ <ul class="nav">
45
+ <li><%= link_to "Statistics", best_boy_admin_stats_path %></li>
46
+ <li><%= link_to "List & Logs", best_boy_admin_lists_path %></li>
47
+ </ul>
48
+ <% if BestBoy.custom_redirect.present? %>
49
+ <p class="navbar-text pull-right"><%= link_to("back", Rails.application.routes.recognize_path(BestBoy.custom_redirect)) %></p>
50
+ <% end %>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="container">
56
+ <div class="row">
57
+ <%= yield %>
58
+ </div>
59
+ </div>
60
+ <%= yield :javascripts %>
61
+ </body>
62
+ </html>
@@ -16,12 +16,15 @@ Gem::Specification.new do |s|
16
16
  s.rubyforge_project = "best_boy"
17
17
  s.required_rubygems_version = ">= 1.3.6"
18
18
 
19
- s.add_development_dependency("bundler", ">= 1.0.0")
20
- s.add_development_dependency('activerecord', '>= 3.0.0')
21
- s.add_development_dependency('activesupport', '>= 3.0.0')
22
- s.add_development_dependency('sqlite3', '>= 1.3.4')
23
- s.add_development_dependency("rake")
24
- s.add_development_dependency("rspec")
19
+ s.add_dependency('kaminari')
20
+
21
+ s.add_development_dependency('bundler', '~> 1.1.0')
22
+ s.add_development_dependency('activerecord')
23
+ s.add_development_dependency('activesupport')
24
+ s.add_development_dependency('sqlite3', '~> 1.3.6')
25
+ s.add_development_dependency('rake', '~> 0.9.2.2')
26
+ s.add_development_dependency('rspec', '~> 2.10.0')
27
+ s.add_development_dependency('shoulda', '~> 3.0.1')
25
28
 
26
29
  s.files = `git ls-files`.split("\n")
27
30
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -0,0 +1,5 @@
1
+ Rails.application.routes.draw do |map|
2
+ get "best_boy_admin" => "best_boy/best_boy_events#index", :as => :best_boy_admin
3
+ get "best_boy_admin/stats" => "best_boy/best_boy_events#stats", :as => :best_boy_admin_stats
4
+ get "best_boy_admin/lists" => "best_boy/best_boy_events#lists", :as => :best_boy_admin_lists
5
+ end
Binary file
@@ -2,8 +2,12 @@ require "best_boy/engine.rb"
2
2
 
3
3
  module BestBoy
4
4
  # Define ORM
5
- mattr_accessor :orm
5
+ mattr_accessor :orm, :base_controller, :before_filter, :skip_before_filter, :custom_redirect
6
6
  @@orm = :active_record
7
+ @@base_controller = "ApplicationController"
8
+ @@before_filter = nil
9
+ @@skip_before_filter = nil
10
+ @@custom_redirect = nil
7
11
 
8
12
  # Load configuration from initializer
9
13
  def self.setup
@@ -12,4 +12,4 @@ module BestBoyController
12
12
  end
13
13
  end
14
14
  end
15
- end
15
+ end
@@ -3,8 +3,8 @@ require "rails"
3
3
 
4
4
  module BestBoy
5
5
  class Engine < Rails::Engine
6
+
6
7
  initializer 'best_boy.model' do |app|
7
- require "#{root}/app/models/best_boy/eventable.rb"
8
8
  if BestBoy.orm == :active_record
9
9
  require "best_boy/models/active_record/best_boy_event.rb"
10
10
  require "best_boy/models/active_record/best_boy/eventable.rb"
@@ -15,9 +15,11 @@ module BestBoy
15
15
  end
16
16
 
17
17
  initializer 'best_boy.controller' do
18
+ require "best_boy/controllers/best_boy_controller.rb"
18
19
  ActiveSupport.on_load(:action_controller) do
19
20
  include BestBoyController::InstanceMethods
20
21
  end
21
22
  end
23
+
22
24
  end
23
25
  end
@@ -2,6 +2,10 @@ module BestBoy
2
2
  module Eventable
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ def eventable?
6
+ true
7
+ end
8
+
5
9
  module ClassMethods
6
10
  def has_a_best_boy
7
11
  # meta programming
@@ -1,4 +1,5 @@
1
1
  class BestBoyEvent < ActiveRecord::Base
2
+
2
3
  # associations
3
4
  #
4
5
  #
@@ -9,9 +10,18 @@ class BestBoyEvent < ActiveRecord::Base
9
10
  #
10
11
  validates :event, :presence => true
11
12
 
13
+ # scopes
14
+ #
15
+ #
16
+
17
+ scope :per_day, lambda { |date| where("best_boy_events.created_at BETWEEN ? AND ?", date.beginning_of_day, date.end_of_day) }
18
+ scope :per_week, lambda { |date| where("best_boy_events.created_at BETWEEN ? AND ?", date.beginning_of_week, date.end_of_week) }
19
+ scope :per_month, lambda { |date| where("best_boy_events.created_at BETWEEN ? AND ?", date.beginning_of_month, date.end_of_month) }
20
+ scope :per_year, lambda { |date| where("best_boy_events.created_at BETWEEN ? AND ?", date.beginning_of_year, date.end_of_year) }
21
+
12
22
  # attributes
13
23
  #
14
24
  #
15
- attr_accessible :owner_id, :owner_type, :event
25
+ attr_accessible :owner_id, :owner_type, :event, :updated_at, :created_at
16
26
 
17
27
  end
@@ -1,3 +1,3 @@
1
1
  module BestBoy
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -6,6 +6,12 @@ module BestBoy
6
6
 
7
7
  def copy_config_file
8
8
  template 'best_boy.rb', 'config/initializers/best_boy.rb'
9
+ template 'bootstrap/glyphicons-halflings-white.png', 'public/images/bootstrap/glyphicons-halflings-white.png'
10
+ template 'bootstrap/glyphicons-halflings.png', 'public/images/bootstrap/glyphicons-halflings.png'
11
+ template 'bootstrap/bootstrap.css', 'public/stylesheets/bootstrap.css'
12
+ #datepicker by Stefan Petre (http://www.eyecon.ro/bootstrap-datepicker)
13
+ template 'bootstrap/bootstrap_datepicker.css', 'public/stylesheets/bootstrap_datepicker.css'
14
+ template 'bootstrap/bootstrap_datepicker.js', 'public/javascripts/bootstrap_datepicker.js'
9
15
  end
10
16
  end
11
17
  end