sequel-reporter 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -35,11 +35,14 @@ The `query` helper takes a block of SQL and returns a `Sequel::Reporter::Report`
35
35
 
36
36
  Sequel Reporter uses [Twitter Bootstrap](http://twitter.github.com/bootstrap) for formatting, so you can use whatever you want to format your reports from there.
37
37
 
38
- The `table` helper takes a query produced by the `query` helper and some options and builds an HTML table. Also, it can take a `:links` option which will linkify values in the table. Here's an example:
38
+ The `table` helper takes a query produced by the `query` helper and some options and builds an HTML table. This helper can take an optional block where you can set more options on the table. For example:
39
39
 
40
- :links => {"Account" => "/reports/register?account=:1"}
40
+ <%= table(@query) do |t|
41
+ t.link /Account/ => '/register?account=:this'
42
+ t.decorate /Amount/ => Sequel::Reporter::NumberDecorator.new
43
+ end %>
41
44
 
42
- This says that every value in the `Account` column will be surrounded with an `<a>` tag pointing at `/reports/register?account=:1`, where `:1` will be replaced by the value in column 1 of that particular row. You can also use `:title` in a link template. It will get replaced with the title of the column that is currently getting linked. In this case, `:title` would get replaced with `Account`.
45
+ The link method links columns matching the given regex to the given URL pattern. URL patterns can reference the value in the current cell using `:this`, the current date with `:now`, the title of the column with `:title`, and any other value from the row using `:N`, where `N` is the 0-indexed row index. You can write your own decorators. See `sequel-reporter/decorators.rb` for examples.
43
46
 
44
47
  ### Reports as Classes
45
48
 
@@ -0,0 +1,61 @@
1
+ require 'rack/utils'
2
+ require 'cgi'
3
+
4
+ module Sequel::Reporter
5
+
6
+ class NumberDecorator
7
+ def initialize(precision=2)
8
+ @precision = precision
9
+ end
10
+
11
+ def decorate(cell, row)
12
+ if cell.value.is_a?(Numeric)
13
+ cell.align = 'right'
14
+ cell.text = sprintf("%0.#{@precision}f", cell.value)
15
+ end
16
+ cell
17
+ end
18
+ end
19
+
20
+ class LinkDecorator
21
+ def initialize(href_pattern)
22
+ @href_pattern = href_pattern
23
+ end
24
+
25
+ def decorate(cell, row)
26
+ url = String.new(@href_pattern)
27
+ row.each_with_index do |c,i|
28
+ url.gsub!(":#{i}", CGI.escape(c.value.to_s))
29
+ end
30
+ url.gsub!(':title', CGI.escape(cell.title.to_s))
31
+ url.gsub!(':now', CGI.escape(DateTime.now.strftime('%Y-%m-%d')))
32
+ url.gsub!(':this', CGI.escape(cell.value.to_s))
33
+ prev_text = cell.text
34
+ cell.text = "<a href=\"#{url}\">#{cell.text}</a>"
35
+ cell
36
+ end
37
+ end
38
+
39
+ class IconDecorator
40
+ def initialize(icon)
41
+ @icon = icon
42
+ end
43
+
44
+ def decorate(cell, row)
45
+ cell.text = "<i class=\"icon-#{@icon}\"></i>"
46
+ cell
47
+ end
48
+ end
49
+
50
+ class HighlightDecorator
51
+ def initialize(color)
52
+ @color = color
53
+ end
54
+
55
+ def decorate(cell, row)
56
+ cell.style['background-color'] = @color
57
+ cell
58
+ end
59
+ end
60
+
61
+ end
@@ -12,8 +12,11 @@ module Sequel::Reporter
12
12
  end
13
13
 
14
14
  def table(report, options = {})
15
- links = options[:links] || {}
16
- partial(:table, :report => report, :links => links, :render_engine => :erb)
15
+ Table.new(report) do |t|
16
+ t.decorate :all => NumberDecorator.new
17
+ t.attributes[:class] = 'table table-striped table-hover table-bordered table-condensed'
18
+ yield t if block_given?
19
+ end.render
17
20
  end
18
21
 
19
22
  def query(options={}, &block)
@@ -25,29 +28,6 @@ module Sequel::Reporter
25
28
  report
26
29
  end
27
30
 
28
- def linkify(links, row, value, display_value)
29
- links.each do |key, val|
30
- if key.is_a? String
31
- key = /^#{key}$/
32
- end
33
-
34
- if key.match(value[1].title.to_s)
35
- url = String.new(links[key])
36
- row.each_with_index do |v,i|
37
- url.gsub!(":#{i}", CGI.escape(v[0].to_s))
38
- end
39
-
40
- url.gsub!(':title', CGI.escape(value[1].title.to_s))
41
- url.gsub!(':now', CGI.escape(DateTime.now.strftime('%Y-%m-%d')))
42
- display_value = "<a href='#{url}'>#{escape_html(display_value)}</a>"
43
- else
44
- display_value = escape_html(display_value)
45
- end
46
-
47
- end
48
- display_value
49
- end
50
-
51
31
  def protected!
52
32
  unless self.send(settings.authorization_func)
53
33
  response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
@@ -1,47 +1,17 @@
1
1
  module Sequel::Reporter
2
- class Field
3
2
 
4
- attr_reader :title, :value_type, :span_class
3
+ class Cell
4
+ attr_reader :title, :value, :style
5
+ attr_accessor :text, :align
5
6
 
6
- def initialize(title, value_type, span_class)
7
+ def initialize(title, value)
7
8
  @title = title
8
- @value_type = value_type
9
- @span_class = span_class
9
+ @value = value
10
+ @style = {}
11
+ @text = value
12
+ @align = 'left'
10
13
  end
11
14
 
12
- def ==(other)
13
- self.title == other.title && \
14
- self.value_type == other.value_type && \
15
- self.span_class == other.span_class
16
- end
17
- end
18
-
19
- class NumberField < Field
20
- def initialize(title)
21
- super(title, 'number', 'pull-right')
22
- end
23
- end
24
-
25
- class StringField < Field
26
- def initialize(title)
27
- super(title, 'string', 'pull-left')
28
- end
29
- end
30
-
31
- class Value
32
- def initialize(val)
33
- @val = val
34
- end
35
-
36
- def to_s
37
- @val
38
- end
39
- end
40
-
41
- class NumericValue < Value
42
- def to_s
43
- sprintf("%0.2f", @val)
44
- end
45
15
  end
46
16
 
47
17
  class Report
@@ -72,18 +42,13 @@ module Sequel::Reporter
72
42
  raise "No data"
73
43
  end
74
44
  ds.columns.each do |col|
75
- value = row[col]
76
- if value.is_a? Numeric
77
- report.add_field NumberField.new(col.to_s)
78
- else
79
- report.add_field StringField.new(col.to_s)
80
- end
45
+ report.add_field col.to_s
81
46
  end
82
47
 
83
48
  ds.each do |row|
84
49
  vals = []
85
50
  ds.columns.each do |col|
86
- vals << row[col]
51
+ vals << Cell.new(col.to_s, row[col])
87
52
  end
88
53
  report.add_row(vals)
89
54
  end
@@ -110,9 +75,9 @@ module Sequel::Reporter
110
75
  @rows << row
111
76
  end
112
77
 
113
- def each_row
78
+ def each
114
79
  @rows.each do |row|
115
- yield row.zip(@fields)
80
+ yield row
116
81
  end
117
82
  end
118
83
 
@@ -121,7 +86,7 @@ module Sequel::Reporter
121
86
 
122
87
  bucket_column_index = 0
123
88
  self.fields.each_with_index do |f, i|
124
- if f.title == column
89
+ if f == column
125
90
  bucket_column_index = i
126
91
  break
127
92
  else
@@ -132,14 +97,13 @@ module Sequel::Reporter
132
97
  buckets = {}
133
98
  new_rows = {}
134
99
 
135
- self.each_row do |row|
136
- key = row[0, bucket_column_index].map { |r| r[0] }
137
- bucket_name = row[bucket_column_index][0]
138
- bucket_value = row[bucket_column_index + 1][0]
100
+ self.each do |row|
101
+ key = row[0, bucket_column_index].map { |r| r.value }
102
+ bucket_name = row[bucket_column_index].value
103
+ bucket_value = row[bucket_column_index + 1].value
139
104
 
140
105
  if not buckets.has_key? bucket_name
141
- field = bucket_value.is_a?(Numeric) ? NumberField.new(bucket_name) : StringField.new(bucket_name)
142
- buckets[bucket_name] = field
106
+ buckets[bucket_name] = bucket_name
143
107
  end
144
108
 
145
109
  new_rows[key] ||= {}
@@ -0,0 +1,60 @@
1
+ module Sequel::Reporter
2
+ class Table
3
+
4
+ attr_reader :attributes
5
+
6
+ def initialize(report)
7
+ @report = report
8
+ @decorators = []
9
+ @attributes = {}
10
+ yield self if block_given?
11
+ end
12
+
13
+ def decorate decorator
14
+ @decorators << decorator
15
+ end
16
+
17
+ def clear_decorators
18
+ @decorators.clear
19
+ end
20
+
21
+ def link href
22
+ if_clause = href.delete(:if)
23
+ href[href.keys.first] = LinkDecorator.new(href.values.first)
24
+ href[:if] = if_clause
25
+ @decorators << href
26
+ end
27
+
28
+ def render
29
+ body_rows = []
30
+ header_aligns = {}
31
+
32
+ @report.each do |row|
33
+ body_rows << row.each_with_index.map do |cell, cell_index|
34
+ @decorators.each do |decorator|
35
+ dec = decorator.dup
36
+ if_clause = dec.delete(:if)
37
+ matcher = dec.keys.first
38
+
39
+ next unless matcher == :all || cell.title =~ matcher
40
+ if if_clause
41
+ next unless if_clause.call(cell, row)
42
+ end
43
+ cell = dec[matcher].decorate(cell, row)
44
+ header_aligns[cell_index] = cell.align
45
+ end
46
+
47
+ style = cell.style.map { |key, val| "#{key}:#{val}"}.join(";")
48
+ %Q{<td style="#{style}"><span class="pull-#{cell.align}">#{cell.text}</span></td>}
49
+ end.join("")
50
+ end
51
+
52
+ body = "<tbody>" + body_rows.map { |r| "<tr>#{r}</tr>" }.join("") + "</tbody>"
53
+ header = "<thead><tr>" + @report.fields.each_with_index.map { |f,i| "<th><span class=\"pull-#{header_aligns[i] || 'left'}\">#{f}</span></th>" }.join("") + "</tr></thead>"
54
+
55
+ attrs = attributes.map { |key,val| "#{key}=\"#{val}\"" }.join(" ")
56
+ "<table #{attrs}>#{header}#{body}</table>"
57
+ end
58
+ end
59
+
60
+ end
@@ -1,5 +1,5 @@
1
1
  module Sequel
2
2
  module Reporter
3
- VERSION = "0.0.6"
3
+ VERSION = "0.0.7"
4
4
  end
5
5
  end
@@ -1,7 +1,9 @@
1
1
  require "sequel-reporter/version"
2
2
  require "sequel-reporter/cli"
3
3
  require "sequel-reporter/report"
4
+ require "sequel-reporter/decorators"
4
5
  require "sequel-reporter/helpers"
6
+ require "sequel-reporter/table"
5
7
  require "sequel-reporter/application"
6
8
 
7
9
  module Sequel
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_dependency('thor')
21
21
  gem.add_dependency('rspec')
22
- gem.add_dependency('sinatra')
22
+ gem.add_dependency('sinatra', '1.4.2')
23
23
  gem.add_dependency('sinatra-contrib')
24
24
  gem.add_dependency('sequel')
25
25
 
data/test/report_spec.rb CHANGED
@@ -11,23 +11,23 @@ describe Sequel::Reporter::Report do
11
11
  it "should run a query" do
12
12
  report = Sequel::Reporter::Report.from_query(@db, "select count(1) as foo from things")
13
13
  rows = []
14
- report.each_row do |row|
14
+ report.each do |row|
15
15
  rows << row
16
16
  end
17
17
 
18
- rows[0][0][0].should eq(2)
19
- rows[0][0][1].should eq(Sequel::Reporter::NumberField.new('foo'))
18
+ rows[0][0].value.should eq(2)
19
+ rows[0][0].title.should eq('foo')
20
20
  end
21
21
 
22
22
  it "should use params" do
23
23
  Sequel::Reporter::Report.params = {:blah => "hi"}
24
24
  report = Sequel::Reporter::Report.from_query(@db, "select count(1) as foo from things where something = :blah")
25
25
  rows = []
26
- report.each_row do |row|
26
+ report.each do |row|
27
27
  rows << row
28
28
  end
29
29
 
30
- rows[0][0][0].should eq(1)
30
+ rows[0][0].value.should eq(1)
31
31
  end
32
32
  end
33
33
 
@@ -43,9 +43,9 @@ describe Sequel::Reporter::Report do
43
43
  report = report.pivot("something", "asc")
44
44
 
45
45
  report.fields.should eq([
46
- Sequel::Reporter::StringField.new('other'),
47
- Sequel::Reporter::NumberField.new('hi'),
48
- Sequel::Reporter::NumberField.new('ho'),
46
+ 'other',
47
+ 'hi',
48
+ 'ho',
49
49
  ])
50
50
  end
51
51
 
data/test/spec_helper.rb CHANGED
@@ -1,9 +1,7 @@
1
1
  $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
2
2
 
3
3
  require 'rspec'
4
- require 'sequel-reporter/application'
5
- require 'sequel-reporter/report'
6
- require 'sequel-reporter/helpers'
4
+ require 'sequel-reporter'
7
5
  require 'sequel'
8
6
  require 'sqlite3'
9
7
 
@@ -0,0 +1,104 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
2
+
3
+ class RightAlignDecorator
4
+ def decorate(cell, row)
5
+ cell.align = 'right'
6
+ cell
7
+ end
8
+ end
9
+
10
+ describe Sequel::Reporter::Table do
11
+
12
+ before :each do
13
+ @things.insert(:id => 1, :something => "hi", :other => "2013-02-18")
14
+ @things.insert(:id => 2, :something => "ho", :other => "2013-02-19")
15
+ end
16
+
17
+ describe "#render" do
18
+ it "should render" do
19
+ report = Sequel::Reporter::Report.from_query(@db, "select count(1) as foo from things")
20
+ table = Sequel::Reporter::Table.new(report)
21
+
22
+ table.render.should eq("<table ><thead><tr><th><span class=\"pull-left\">foo</span></th></tr></thead><tbody><tr><td style=\"\"><span class=\"pull-left\">2</span></td></tr></tbody></table>")
23
+ end
24
+
25
+ it "should decorate" do
26
+ report = Sequel::Reporter::Report.from_query(@db, "select count(1) as foo from things")
27
+ table = Sequel::Reporter::Table.new(report) do |t|
28
+ t.decorate /foo/ => RightAlignDecorator.new
29
+ end
30
+
31
+ table.render.should eq("<table ><thead><tr><th><span class=\"pull-right\">foo</span></th></tr></thead><tbody><tr><td style=\"\"><span class=\"pull-right\">2</span></td></tr></tbody></table>")
32
+ end
33
+
34
+ it "should decorate with if lambda" do
35
+ report = Sequel::Reporter::Report.from_query(@db, "select something from things order by id")
36
+ table = Sequel::Reporter::Table.new(report) do |t|
37
+ t.decorate /something/ => RightAlignDecorator.new, :if => lambda{|c,r| c.value == 'hi'}
38
+ end
39
+
40
+ table.render.should eq("<table ><thead><tr><th><span class=\"pull-right\">something</span></th></tr></thead><tbody><tr><td style=\"\"><span class=\"pull-right\">hi</span></td></tr><tr><td style=\"\"><span class=\"pull-left\">ho</span></td></tr></tbody></table>")
41
+ end
42
+
43
+ it "should decorate all cells if given all symbol" do
44
+ report = Sequel::Reporter::Report.from_query(@db, "select id, something from things order by id limit 1")
45
+ table = Sequel::Reporter::Table.new(report) do |t|
46
+ t.decorate :all => RightAlignDecorator.new
47
+ end
48
+
49
+ table.render.should eq("<table ><thead><tr><th><span class=\"pull-right\">id</span></th><th><span class=\"pull-right\">something</span></th></tr></thead><tbody><tr><td style=\"\"><span class=\"pull-right\">1</span></td><td style=\"\"><span class=\"pull-right\">hi</span></td></tr></tbody></table>")
50
+ end
51
+
52
+ it "should decorate with multiple" do
53
+ report = Sequel::Reporter::Report.from_query(@db, "select count(1) as foo from things")
54
+ table = Sequel::Reporter::Table.new(report) do |t|
55
+ t.decorate /foo/ => Sequel::Reporter::NumberDecorator.new(3)
56
+ t.decorate /foo/ => Sequel::Reporter::LinkDecorator.new('/something?q=:0&now=:now&title=:title&this=:this')
57
+ end
58
+
59
+ now = DateTime.now.strftime('%Y-%m-%d')
60
+ table.render.should eq("<table ><thead><tr><th><span class=\"pull-right\">foo</span></th></tr></thead><tbody><tr><td style=\"\"><span class=\"pull-right\"><a href=\"/something?q=2&now=2013-04-12&title=foo&this=2\">2.000</a></span></td></tr></tbody></table>")
61
+ end
62
+
63
+ it "should decorate with icon decorator" do
64
+ report = Sequel::Reporter::Report.from_query(@db, "select count(1) as foo from things")
65
+ table = Sequel::Reporter::Table.new(report) do |t|
66
+ t.decorate /foo/ => Sequel::Reporter::IconDecorator.new("glass")
67
+ end
68
+
69
+ now = DateTime.now.strftime('%Y-%m-%d')
70
+ table.render.should eq("<table ><thead><tr><th><span class=\"pull-left\">foo</span></th></tr></thead><tbody><tr><td style=\"\"><span class=\"pull-left\"><i class=\"icon-glass\"></i></span></td></tr></tbody></table>")
71
+ end
72
+
73
+ it "should add attributes to table tag" do
74
+ report = Sequel::Reporter::Report.from_query(@db, "select count(1) as foo from things")
75
+ table = Sequel::Reporter::Table.new(report) do |t|
76
+ t.attributes[:class] = "table table-bordered table-striped table-hover"
77
+ end
78
+
79
+ now = DateTime.now.strftime('%Y-%m-%d')
80
+ table.render.should eq("<table class=\"table table-bordered table-striped table-hover\"><thead><tr><th><span class=\"pull-left\">foo</span></th></tr></thead><tbody><tr><td style=\"\"><span class=\"pull-left\">2</span></td></tr></tbody></table>")
81
+ end
82
+
83
+ it "should decorate with link shortcut" do
84
+ report = Sequel::Reporter::Report.from_query(@db, "select count(1) as foo from things")
85
+ table = Sequel::Reporter::Table.new(report) do |t|
86
+ t.link /foo/ => '/something?q=:0&now=:now&title=:title&this=:this'
87
+ end
88
+
89
+ now = DateTime.now.strftime('%Y-%m-%d')
90
+ table.render.should eq("<table ><thead><tr><th><span class=\"pull-left\">foo</span></th></tr></thead><tbody><tr><td style=\"\"><span class=\"pull-left\"><a href=\"/something?q=2&now=2013-04-12&title=foo&this=2\">2</a></span></td></tr></tbody></table>")
91
+ end
92
+
93
+ it "should decorate with highlight decorator" do
94
+ report = Sequel::Reporter::Report.from_query(@db, "select count(1) as foo from things")
95
+ table = Sequel::Reporter::Table.new(report) do |t|
96
+ t.decorate :all => Sequel::Reporter::HighlightDecorator.new('#00FF00')
97
+ end
98
+
99
+ now = DateTime.now.strftime('%Y-%m-%d')
100
+ table.render.should eq("<table ><thead><tr><th><span class=\"pull-left\">foo</span></th></tr></thead><tbody><tr><td style=\"background-color:#00FF00\"><span class=\"pull-left\">2</span></td></tr></tbody></table>")
101
+ end
102
+
103
+ end
104
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel-reporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
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-02-23 00:00:00.000000000 Z
12
+ date: 2013-04-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -48,17 +48,17 @@ 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: 1.4.2
54
54
  type: :runtime
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: 1.4.2
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: sinatra-contrib
64
64
  requirement: !ruby/object:Gem::Requirement
@@ -138,16 +138,18 @@ files:
138
138
  - data/views/error.erb
139
139
  - data/views/layout.erb
140
140
  - data/views/reports/dashboard.erb
141
- - data/views/table.erb
142
141
  - lib/sequel-reporter.rb
143
142
  - lib/sequel-reporter/application.rb
144
143
  - lib/sequel-reporter/cli.rb
144
+ - lib/sequel-reporter/decorators.rb
145
145
  - lib/sequel-reporter/helpers.rb
146
146
  - lib/sequel-reporter/report.rb
147
+ - lib/sequel-reporter/table.rb
147
148
  - lib/sequel-reporter/version.rb
148
149
  - sequel-reporter.gemspec
149
150
  - test/report_spec.rb
150
151
  - test/spec_helper.rb
152
+ - test/table_spec.rb
151
153
  homepage: https://github.com/peterkeen/sequel-reporter
152
154
  licenses: []
153
155
  post_install_message:
@@ -175,3 +177,4 @@ summary: An opinionated framework for writing reporting applications
175
177
  test_files:
176
178
  - test/report_spec.rb
177
179
  - test/spec_helper.rb
180
+ - test/table_spec.rb
data/data/views/table.erb DELETED
@@ -1,31 +0,0 @@
1
- <% if report.error %>
2
- <div class="alert-message block-message warning">
3
- <p><%= report.error %></p>
4
- <% if report.error.to_s != "No data" %>
5
- <p><%= report.error.backtrace %></p>
6
- <% end %>
7
- </div>
8
- <% else %>
9
- <table class="table table-striped table-borderd table-hover table-condensed sorted">
10
- <thead>
11
- <tr>
12
- <% report.fields.each do |f| %>
13
- <th><span class="<%= f.span_class %>"><%= f.title %></span></th>
14
- <% end %>
15
- </tr>
16
- </thead>
17
- <tbody>
18
- <% report.each_row do |row| %>
19
- <tr>
20
- <% row.each do |val| %>
21
- <td>
22
- <span class="<%= val[1].span_class %>">
23
- <%= linkify(links, row, val, (val[1].value_type == 'number' && ! val[0].nil?) ? sprintf("%0.2f", val[0]) : val[0]) %>
24
- </span>
25
- </td>
26
- <% end %>
27
- </tr>
28
- <% end %>
29
- </tbody>
30
- </table>
31
- <% end %>