sequel-reporter 0.0.6 → 0.0.7

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.
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 %>