table_cloth 0.0.1 → 0.1.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.
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/table_cloth/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
data/README.md CHANGED
@@ -1,29 +1,82 @@
1
- # Tablecloth
1
+ # Table Cloth
2
2
 
3
- TODO: Write a gem description
3
+ Table Cloth gives you an easy to use DSL for creating and rendering tables in rails.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  Add this line to your application's Gemfile:
8
8
 
9
- gem 'tablecloth'
9
+ gem 'table_cloth'
10
10
 
11
11
  And then execute:
12
12
 
13
13
  $ bundle
14
14
 
15
- Or install it yourself as:
15
+ ## Usage
16
16
 
17
- $ gem install tablecloth
17
+ Table Cloth can use defined tables in app/tables or you can build them on the fly.
18
18
 
19
- ## Usage
19
+ Table models can be generated using rails generators.
20
+
21
+ ```
22
+ $ rails g table User
23
+ ```
24
+
25
+ It will make this:
26
+
27
+ ```
28
+ class UserTable < TableCloth::Base
29
+ # Define columns with the #column method
30
+ # column :name, :email
31
+
32
+ # Columns can be provided a block
33
+ #
34
+ # column :name do |object|
35
+ # object.downcase
36
+ # end
37
+ #
38
+ # Columns can also have conditionals if you want.
39
+ # The conditions are checked against the table's methods.
40
+ # As a convience, the table has a #view method which will return the current view context.
41
+ # This gives you access to current user, params, etc...
42
+ #
43
+ # column :email, if: :admin?
44
+ #
45
+ # def admin?
46
+ # view.current_user.admin?
47
+ # end
48
+ #
49
+ # Actions give you the ability to create a column for any actions you'd like to provide.
50
+ # Pass a block with an arity of 2, (object, view context).
51
+ # You can add as many actions as you want.
52
+ #
53
+ # action {|object, view| view.link_to "Edit", edit_object_path(object) }
54
+ end
55
+ ```
56
+
57
+ Go ahead and modify it to suit your needs, pick the columns, conditions, actions, etc...
58
+
59
+ In your view, you would then use this code:
60
+ ```
61
+ <%= simple_table_for @users, with: UserTable %>
62
+
63
+ ```
64
+
65
+ The second approach to making tables with Table Cloth is in the view.
20
66
 
21
- TODO: Write usage instructions here
67
+ ```
68
+ <%= simple_table_for @users do |t| %>
69
+ <% t.column :name %>
70
+ <% t.column :email %>
71
+ <% t.action {|user| link_to "View", user %>
72
+ <% end %>
73
+ ```
22
74
 
23
75
  ## Contributing
24
76
 
25
77
  1. Fork it
26
78
  2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
79
+ 3. CREATE A SPEC.
80
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
81
+ 5. Push to the branch (`git push origin my-new-feature`)
82
+ 6. Create new Pull Request
@@ -0,0 +1,7 @@
1
+ class TableGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path("../templates", __FILE__)
3
+
4
+ def create_uploader_file
5
+ template "table.rb", "app/tables/#{file_name}_table.rb"
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ class <%= class_name %>Table < TableCloth::Base
2
+ # Define columns with the #column method
3
+ # column :name, :email
4
+
5
+ # Columns can be provided a block
6
+ #
7
+ # column :name do |object|
8
+ # object.downcase
9
+ # end
10
+ #
11
+ # Columns can also have conditionals if you want.
12
+ # The conditions are checked against the table's methods.
13
+ # As a convience, the table has a #view method which will return the current view context.
14
+ # This gives you access to current user, params, etc...
15
+ #
16
+ # column :email, if: :admin?
17
+ #
18
+ # def admin?
19
+ # view.current_user.admin?
20
+ # end
21
+ #
22
+ # Actions give you the ability to create a column for any actions you'd like to provide.
23
+ # Pass a block with an arity of 2, (object, view context).
24
+ # You can add as many actions as you want.
25
+ #
26
+ # action {|object, view| view.link_to "Edit", edit_object_path(object) }
27
+ end
@@ -0,0 +1,9 @@
1
+ module TableCloth
2
+ class Action
3
+ attr_reader :action, :options
4
+
5
+ def initialize(options)
6
+ @options = options
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module TableCloth
2
+ module ActionViewExtension
3
+ def simple_table_for(objects, options={}, &block)
4
+ view_context = self
5
+ table = if block_given?
6
+ TableCloth::Builder.build(objects, view_context, options) do |table|
7
+ yield table
8
+ end
9
+ else
10
+ TableCloth::Builder.build(objects, view_context, options)
11
+ end
12
+
13
+ table.to_s
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,81 @@
1
+ module TableCloth
2
+ class Base
3
+ attr_reader :collection, :view
4
+
5
+ def initialize(collection, view)
6
+ @collection = collection
7
+ @view = view
8
+ end
9
+
10
+ def column_names
11
+ columns.inject([]) do |names, (column_name, column)|
12
+ names << column.human_name; names
13
+ end
14
+ end
15
+
16
+ def columns
17
+ self.class.columns.inject({}) do |columns, (column_name, column)|
18
+ if column.available?(self)
19
+ columns[column_name] = column
20
+ end
21
+ columns
22
+ end
23
+ end
24
+
25
+ def has_actions?
26
+ self.class.has_actions?
27
+ end
28
+
29
+ class << self
30
+ def presenter(klass=nil)
31
+ if klass
32
+ @presenter = klass
33
+ else
34
+ @presenter || (superclass.respond_to?(:presenter) ? superclass.presenter : raise("No Presenter"))
35
+ end
36
+ end
37
+
38
+ def column(*args, &block)
39
+ options = args.extract_options! || {}
40
+ options[:proc] = block if block_given?
41
+
42
+ args.each do |name|
43
+ add_column name, Column.new(name, options)
44
+ end
45
+ end
46
+
47
+ def columns
48
+ @columns ||= {}
49
+ if superclass.respond_to? :columns
50
+ @columns = superclass.columns.merge(@columns)
51
+ end
52
+
53
+ @columns
54
+ end
55
+
56
+ def add_column(name, column)
57
+ @columns ||= {}
58
+ @columns[name] = column
59
+ end
60
+
61
+ def action(*args, &block)
62
+ options = args.extract_options! || {}
63
+ options[:proc] = block if block_given?
64
+
65
+ add_action Action.new(options)
66
+ end
67
+
68
+ def add_action(action)
69
+ unless has_actions?
70
+ columns[:actions] = Columns::Action.new(:actions)
71
+ end
72
+
73
+ columns[:actions].actions << action
74
+ end
75
+
76
+ def has_actions?
77
+ columns[:actions].present?
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,29 @@
1
+ module TableCloth
2
+ class Builder
3
+ attr_accessor :table, :presenter
4
+
5
+ def self.build(objects, view, options={}, &block)
6
+ if block_given?
7
+ table = Class.new(TableCloth::Base)
8
+ block.call(table)
9
+ else
10
+ table_class = options.delete(:with)
11
+ table = table_class.kind_of?(String) ? table_class.constantize : table_class
12
+ end
13
+
14
+ presenter = options.delete(:present_with) || table.presenter
15
+
16
+ new.tap do |builder|
17
+ builder.table = table
18
+ builder.presenter = presenter.new(objects, table, view)
19
+ end
20
+ end
21
+
22
+ def initialize
23
+ end
24
+
25
+ def to_s
26
+ presenter.render_table
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ module TableCloth
2
+ class Column
3
+ attr_reader :options, :name
4
+
5
+ def initialize(name, options={})
6
+ @name = name
7
+ @options = options
8
+ end
9
+
10
+ def value(object, view)
11
+ if options[:proc] && options[:proc].respond_to?(:call)
12
+ view.capture(object, options, view, &options[:proc])
13
+ else
14
+ object.send(name)
15
+ end
16
+ end
17
+
18
+ def human_name
19
+ name.to_s.humanize
20
+ end
21
+
22
+ def available?(table)
23
+ if options[:if] && options[:if].is_a?(Symbol)
24
+ return !!table.send(options[:if])
25
+ end
26
+
27
+ if options[:unless] && options[:unless].is_a?(Symbol)
28
+ return !table.send(options[:unless])
29
+ end
30
+
31
+ true
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ module TableCloth
2
+ module Columns
3
+ class Action < Column
4
+ def value(object, view_context)
5
+ actions_html = actions.inject('') do |links, action|
6
+ links + "\n" + view_context.capture(object, view_context, &action.options[:proc])
7
+ end
8
+
9
+ view_context.raw(actions_html)
10
+ end
11
+
12
+ def actions
13
+ @actions ||= []
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,9 +1,27 @@
1
1
  module TableCloth
2
2
  class Configuration
3
- cattr_accessor :table_class
4
-
5
- def self.configure(&block)
6
- yield
3
+ cattr_accessor :table
4
+ cattr_accessor :thead
5
+ cattr_accessor :th
6
+ cattr_accessor :tbody
7
+ cattr_accessor :tr
8
+ cattr_accessor :td
9
+
10
+ self.table = ActiveSupport::OrderedOptions.new
11
+ self.thead = ActiveSupport::OrderedOptions.new
12
+ self.th = ActiveSupport::OrderedOptions.new
13
+ self.tbody = ActiveSupport::OrderedOptions.new
14
+ self.tr = ActiveSupport::OrderedOptions.new
15
+ self.td = ActiveSupport::OrderedOptions.new
16
+
17
+ class << self
18
+ def configure(&block)
19
+ block.arity > 0 ? block.call(self) : yield
20
+ end
21
+
22
+ def config_for(type)
23
+ self.send(type).to_hash
24
+ end
7
25
  end
8
26
  end
9
27
  end
@@ -0,0 +1,54 @@
1
+ module TableCloth
2
+ class Presenter
3
+ attr_reader :view_context, :table_definition, :objects,
4
+ :table
5
+
6
+ def initialize(objects, table, view)
7
+ @view_context = view
8
+ @table_definition = table
9
+ @objects = objects
10
+ @table = table_definition.new(objects, view)
11
+ end
12
+
13
+ # Short hand so your fingers don't hurt
14
+ def v
15
+ view_context
16
+ end
17
+
18
+ def render_table
19
+ raise NoMethodError, "You must override the .render method"
20
+ end
21
+
22
+ def render_header
23
+ raise NoMethodError, "You must override the .header method"
24
+ end
25
+
26
+ def render_rows
27
+ raise NoMethodError, "You must override the .rows method"
28
+ end
29
+
30
+ def column_names
31
+ table.column_names
32
+ end
33
+
34
+ def row_values(object)
35
+ column_values = table.columns.inject([]) do |values, (key, column)|
36
+ values << column.value(object, view_context); values
37
+ end
38
+ end
39
+
40
+ def rows
41
+ objects.inject([]) do |row, object|
42
+ row << row_values(object); row
43
+ end
44
+ end
45
+
46
+ def wrapper_tag(type, value=nil, &block)
47
+ content = if block_given?
48
+ v.content_tag(type, TableCloth.config_for(type), &block)
49
+ else
50
+ v.content_tag(type, value, TableCloth.config_for(type))
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,43 @@
1
+ module TableCloth
2
+ module Presenters
3
+ class Default < ::TableCloth::Presenter
4
+ def render_table
5
+ wrapper_tag :table do
6
+ render_header + render_rows
7
+ end
8
+ end
9
+
10
+ def render_rows
11
+ wrapper_tag :tbody do
12
+ body = rows.inject('') do |r, values|
13
+ r + render_row(values)
14
+ end
15
+
16
+ v.raw(body)
17
+ end
18
+ end
19
+
20
+ def render_row(values)
21
+ wrapper_tag :tr do
22
+ row = values.inject('') do |tds, value|
23
+ tds + wrapper_tag(:td, value)
24
+ end
25
+
26
+ v.raw(row)
27
+ end
28
+ end
29
+
30
+ def render_header
31
+ wrapper_tag :thead do
32
+ wrapper_tag(:tr) do
33
+ names = column_names.inject('') do |tags, name|
34
+ tags + wrapper_tag(:th, name)
35
+ end
36
+
37
+ v.raw(names)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,3 +1,3 @@
1
1
  module TableCloth
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/table_cloth.rb CHANGED
@@ -1,7 +1,29 @@
1
- require 'table_cloth/version'
2
- require 'table_cloth/configuration'
3
1
  require 'action_view'
2
+ require 'table_cloth/version'
3
+ require 'table_cloth/base'
4
4
 
5
5
  module TableCloth
6
-
7
- end
6
+ autoload :Configuration, 'table_cloth/configuration'
7
+ autoload :Builder, 'table_cloth/builder'
8
+ autoload :Column, 'table_cloth/column'
9
+ autoload :Action, 'table_cloth/action'
10
+ autoload :Presenter, 'table_cloth/presenter'
11
+ autoload :ActionViewExtension, 'table_cloth/action_view_extension'
12
+
13
+ module Presenters
14
+ autoload :Default, 'table_cloth/presenters/default'
15
+ end
16
+
17
+ module Columns
18
+ autoload :Action, 'table_cloth/columns/action'
19
+ end
20
+
21
+ extend self
22
+ def self.config_for(type)
23
+ Configuration.config_for(type)
24
+ end
25
+ end
26
+
27
+ TableCloth::Base.presenter ::TableCloth::Presenters::Default
28
+
29
+ ActionView::Base.send(:include, TableCloth::ActionViewExtension)
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Action View Extension' do
4
+ let(:action_view) { ActionView::Base.new }
5
+ let(:objects) do
6
+ 3.times.map do |n|
7
+ num = n+1
8
+ DummyModel.new.tap do |d|
9
+ d.id = num # Wat
10
+ d.email = "robert#{num}@example.com"
11
+ d.name = "robert#{num}"
12
+ end
13
+ end
14
+ end
15
+
16
+ it 'includes to actionview' do
17
+ action_view.should respond_to :simple_table_for
18
+ end
19
+
20
+ it '.simple_table_for renders a table' do
21
+ table = action_view.simple_table_for(objects) do |table|
22
+ table.column :name, :email
23
+ end
24
+
25
+ doc = Nokogiri::HTML(table)
26
+ doc.at_xpath('//table').should be_present
27
+ doc.at_xpath('//tr').xpath('.//th').length.should == 2
28
+
29
+ trs = doc.at_xpath('//tbody').xpath('.//tr').to_a
30
+ trs.each_with_index do |tr, index|
31
+ tds = tr.xpath('.//td')
32
+ objects[index].name.should == tds[0].text
33
+ objects[index].email.should == tds[1].text
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ describe TableCloth::Base do
4
+ subject { Class.new(TableCloth::Base) }
5
+ let(:view_context) { ActionView::Base.new }
6
+
7
+ context 'columns' do
8
+ it 'has a column method' do
9
+ subject.should respond_to :column
10
+ end
11
+
12
+ it 'column accepts a name' do
13
+ expect { subject.column :column_name }.not_to raise_error
14
+ end
15
+
16
+ it 'column accepts options' do
17
+ expect { subject.column :n, {option: 'value'} }.not_to raise_error
18
+ end
19
+
20
+ it '.columns returns all columns' do
21
+ subject.column :name
22
+ subject.columns.size.should == 1
23
+ end
24
+
25
+ it 'excepts multiple column names' do
26
+ subject.column :name, :email
27
+ subject.columns.size.should == 2
28
+ end
29
+
30
+ it 'stores a proc if given in options' do
31
+ subject.column(:name) { 'Wee' }
32
+
33
+ column = subject.columns[:name]
34
+ column.options[:proc].should be_present
35
+ column.options[:proc].should be_kind_of(Proc)
36
+ end
37
+
38
+ it '.column_names returns all names' do
39
+ subject.column :name, :email
40
+ subject.new([], view_context).column_names.should == ['Name', 'Email']
41
+ end
42
+
43
+ it '.column_names includes actions when given' do
44
+ subject.action { '/' }
45
+ subject.new([], view_context).column_names.should include 'Actions'
46
+ end
47
+ end
48
+
49
+ context 'conditions' do
50
+ let(:dummy_model) do
51
+ DummyModel.new.tap do |d|
52
+ d.id = 1
53
+ d.email = 'robert@example.com'
54
+ d.name = 'robert'
55
+ end
56
+ end
57
+
58
+ context 'if' do
59
+ subject { DummyTable.new([dummy_model], view_context) }
60
+
61
+ it 'includes the id column when admin' do
62
+ subject.column_names.should include 'Id'
63
+ end
64
+
65
+ it 'exclused the id column when an admin' do
66
+ def subject.admin?
67
+ false
68
+ end
69
+
70
+ subject.column_names.should_not include 'Id'
71
+ end
72
+ end
73
+
74
+ context 'unless' do
75
+ subject { DummyTableUnlessAdmin.new([dummy_model], view_context) }
76
+ before(:each) do
77
+ def subject.admin?
78
+ false
79
+ end
80
+ end
81
+
82
+ it 'includes the id when not an admin' do
83
+ subject.column_names.should include 'Id'
84
+ end
85
+ end
86
+ end
87
+
88
+ context 'presenters' do
89
+ it 'has a presenter method' do
90
+ subject.should respond_to :presenter
91
+ end
92
+ end
93
+
94
+ context 'actions' do
95
+ it 'has an action method' do
96
+ subject.should respond_to :action
97
+ end
98
+
99
+ it 'it adds an acion' do
100
+ subject.action { '/' }
101
+ subject.columns[:actions].actions.size.should == 1
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe TableCloth::Builder do
4
+ subject { Class.new(TableCloth::Builder) }
5
+ let(:view_context) { ActionView::Base.new }
6
+
7
+ context '.build' do
8
+ it 'can build a table on the fly with a block' do
9
+ new_table = subject.build([], view_context) do |table|
10
+ table.column :name
11
+ table.action(:edit) { '/model/1/edit' }
12
+ end
13
+
14
+ new_table.table.columns.length.should == 2
15
+ new_table.table.columns[:actions].actions.length.should == 1
16
+ end
17
+
18
+ it 'can build a table from a class name' do
19
+ new_table = subject.build([], view_context, with: DummyTable)
20
+ new_table.table.should == DummyTable
21
+ end
22
+
23
+ it 'defaults the presenter' do
24
+ new_table = subject.build([], view_context, with: DummyTable)
25
+ new_table.presenter.should be_kind_of TableCloth::Presenters::Default
26
+ end
27
+
28
+ it 'can provide a presenter' do
29
+ random_presenter = Class.new(TableCloth::Presenters::Default)
30
+ new_table = subject.build([], view_context, with: DummyTable, present_with: random_presenter)
31
+ new_table.presenter.should be_kind_of random_presenter
32
+ end
33
+
34
+ it '.to_s renders a table' do
35
+ new_table = subject.build([], view_context, with: DummyTable)
36
+ body = new_table.to_s
37
+ doc = Nokogiri::HTML(body)
38
+ doc.at_xpath('//table').should be_present
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe TableCloth::Column do
4
+ subject { Class.new(TableCloth::Column) }
5
+ let(:view_context) { ActionView::Base.new }
6
+ let(:dummy_model) do
7
+ DummyModel.new.tap do |d|
8
+ d.id = 1
9
+ d.email = 'robert@example.com'
10
+ d.name = 'robert'
11
+ end
12
+ end
13
+
14
+ context 'values' do
15
+ let(:name_column) do
16
+ TableCloth::Column.new(:name)
17
+ end
18
+
19
+ let(:email_column) do
20
+ proc = lambda {|object, options, view|
21
+ object.email
22
+ }
23
+
24
+ TableCloth::Column.new(:my_email, proc: proc)
25
+ end
26
+
27
+ it 'returns the name correctly' do
28
+ name_column.value(dummy_model, view_context).should == 'robert'
29
+ end
30
+
31
+ it 'returns the email from a proc correctly' do
32
+ email_column.value(dummy_model, view_context).should == 'robert@example.com'
33
+ end
34
+
35
+ context '.available?' do
36
+ let(:dummy_table) do
37
+ Class.new(TableCloth::Table) do
38
+ column :name, if: :admin?
39
+
40
+ def admin?
41
+ view.admin?
42
+ end
43
+ end
44
+ end
45
+
46
+ it 'returns true on successful constraint' do
47
+ table = Class.new(DummyTable).new([dummy_model], view_context)
48
+ column = TableCloth::Column.new(:name, if: :admin?)
49
+ column.available?(table).should be_true
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe TableCloth::Columns::Action do
4
+ let(:view_context) { ActionView::Base.new }
5
+ let(:dummy_table) { Class.new(DummyTable) }
6
+ let(:dummy_model) do
7
+ DummyModel.new.tap do |d|
8
+ d.id = 1
9
+ d.email = 'robert@example.com'
10
+ d.name = 'robert'
11
+ end
12
+ end
13
+
14
+ subject { TableCloth::Columns::Action.new(object, view_context) }
15
+
16
+ it '.value returns all actions in HTML' do
17
+ dummy_table.action {|object, view| view.link_to "Edit", "#{object.id}"}
18
+ presenter = TableCloth::Presenters::Default.new([dummy_model], dummy_table, view_context)
19
+
20
+ doc = Nokogiri::HTML(presenter.render_table)
21
+
22
+ actions_column = doc.at_xpath('//tbody')
23
+ .at_xpath('.//tr')
24
+ .xpath('.//td')
25
+ .last
26
+
27
+ link = actions_column.at_xpath('.//a')
28
+ link.should be_present
29
+ link[:href].should == '1'
30
+ end
31
+ end
@@ -3,8 +3,33 @@ require 'spec_helper'
3
3
  describe TableCloth::Configuration do
4
4
  subject { Class.new(TableCloth::Configuration) }
5
5
 
6
- it 'configures default table classes' do
7
- subject.table_class = 'table'
8
- subject.table_class.should == 'table'
6
+ it 'configures table options' do
7
+ subject.table.classes = 'table'
8
+ subject.table.classes.should == 'table'
9
+ end
10
+
11
+ it 'configures thead options' do
12
+ subject.thead.classes = 'thead'
13
+ subject.thead.classes.should == 'thead'
14
+ end
15
+
16
+ it 'configures th options' do
17
+ subject.th.classes = 'th'
18
+ subject.th.classes.should == 'th'
19
+ end
20
+
21
+ it 'configures tbody options' do
22
+ subject.tbody.classes = 'tbody'
23
+ subject.tbody.classes.should == 'tbody'
24
+ end
25
+
26
+ it 'configures tr options' do
27
+ subject.tr.classes = 'tr'
28
+ subject.tr.classes.should == 'tr'
29
+ end
30
+
31
+ it 'configures td options' do
32
+ subject.td.classes = 'td'
33
+ subject.td.classes.should == 'td'
9
34
  end
10
35
  end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe TableCloth::Presenter do
4
+ let(:dummy_table) { Class.new(DummyTable) }
5
+ let(:dummy_model) do
6
+ DummyModel.new.tap do |d|
7
+ d.id = 1
8
+ d.email = 'robert@example.com'
9
+ d.name = 'robert'
10
+ end
11
+ end
12
+
13
+ let(:objects) do
14
+ 3.times.map do |n|
15
+ num = n+1
16
+ DummyModel.new.tap do |d|
17
+ d.id = num # Wat
18
+ d.email = "robert#{num}@example.com"
19
+ d.name = "robert#{num}"
20
+ end
21
+ end
22
+ end
23
+
24
+ let(:view_context) { ActionView::Base.new }
25
+ subject { TableCloth::Presenter.new(objects, dummy_table, view_context) }
26
+
27
+ it 'returns all values for a row' do
28
+ subject.row_values(dummy_model).should == [1, 'robert', 'robert@example.com']
29
+ end
30
+
31
+ it 'returns an edit link in the actions column' do
32
+ dummy_table.action {|object, view| view.link_to 'Edit', '/model/1/edit' }
33
+ presenter = TableCloth::Presenter.new(objects, dummy_table, view_context)
34
+
35
+ column = Nokogiri::HTML(presenter.row_values(dummy_model).last)
36
+ column.at_xpath('//a')[:href].should == '/model/1/edit'
37
+ column.at_xpath('//a').text.should == 'Edit'
38
+ end
39
+
40
+ it 'generates the values for all of the rows' do
41
+ subject.rows.should == [
42
+ [1, 'robert1', 'robert1@example.com'],
43
+ [2, 'robert2', 'robert2@example.com'],
44
+ [3, 'robert3', 'robert3@example.com']
45
+ ]
46
+ end
47
+
48
+ context 'tags' do
49
+ before(:all) { TableCloth::Configuration.table.class = 'stuff' }
50
+ it '.wrapper_tag includes config for a tag in block form' do
51
+ table = subject.wrapper_tag(:table) do
52
+ 'Hello'
53
+ end
54
+ table = Nokogiri::HTML(table)
55
+
56
+ table.at_xpath('//table')[:class].should == 'stuff'
57
+ table.at_xpath('//table').text.should include 'Hello'
58
+ end
59
+
60
+ it '.wrapper_tag includes config for a tag without a block' do
61
+ table = subject.wrapper_tag(:table, 'Hello')
62
+ table = Nokogiri::HTML(table)
63
+ table.at_xpath('//table')[:class].should == 'stuff'
64
+ table.at_xpath('//table').text.should include 'Hello'
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ describe TableCloth::Presenters::Default do
4
+ let(:dummy_table) { DummyTable }
5
+ let(:dummy_model) do
6
+ DummyModel.new.tap do |d|
7
+ d.id = 1
8
+ d.email = 'robert@example.com'
9
+ d.name = 'robert'
10
+ end
11
+ end
12
+
13
+ let(:objects) do
14
+ 3.times.map do |n|
15
+ num = n+1
16
+ DummyModel.new.tap do |d|
17
+ d.id = num # Wat
18
+ d.email = "robert#{num}@example.com"
19
+ d.name = "robert#{num}"
20
+ end
21
+ end
22
+ end
23
+
24
+ let(:view_context) { ActionView::Base.new }
25
+ subject { TableCloth::Presenters::Default.new(objects, dummy_table, view_context) }
26
+
27
+ it 'creates a table head' do
28
+ header = subject.render_header
29
+ doc = Nokogiri::HTML(header)
30
+
31
+ thead = doc.xpath('.//thead')
32
+ thead.should be_present
33
+
34
+ tr = thead.xpath('.//tr')
35
+ tr.should be_present
36
+
37
+ th = tr.xpath('.//th')
38
+ th.should be_present
39
+
40
+ th.length.should == 3
41
+ end
42
+
43
+ it 'creates rows' do
44
+ rows = subject.render_rows
45
+ doc = Nokogiri::HTML(rows)
46
+
47
+ tbody = doc.xpath('//tbody')
48
+ tbody.should be_present
49
+
50
+ tbody.xpath('.//tr').each_with_index do |row, row_index|
51
+ row.xpath('.//td').each_with_index do |td, td_index|
52
+ object = objects[row_index]
53
+ case td_index
54
+ when 0
55
+ object.id.to_s == td.text
56
+ when 1
57
+ object.name.should == td.text
58
+ when 2
59
+ object.email.should == td.text
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ it 'creates an entire table' do
66
+ doc = Nokogiri::HTML(subject.render_table)
67
+ table = doc.xpath('//table')
68
+ table.should be_present
69
+
70
+ thead = table.xpath('.//thead')
71
+ thead.should be_present
72
+
73
+ tbody = table.at_xpath('.//tbody')
74
+ tbody.should be_present
75
+
76
+ tbody.xpath('.//tr').length.should == 3
77
+ end
78
+
79
+ context 'configuration' do
80
+ before(:all) do
81
+ TableCloth::Configuration.configure do |config|
82
+ config.table.class = 'table'
83
+ config.thead.class = 'thead'
84
+ config.th.class = 'th'
85
+ config.tbody.class = 'tbody'
86
+ config.tr.class = 'tr'
87
+ config.td.class = 'td'
88
+ end
89
+ end
90
+
91
+ let(:doc) { Nokogiri::HTML(subject.render_table) }
92
+
93
+ it 'tables have a class attached' do
94
+ doc.at_xpath('//table')[:class].should include 'table'
95
+ end
96
+
97
+ it 'thead has a class attached' do
98
+ doc.at_xpath('//thead')[:class].should include 'thead'
99
+ end
100
+
101
+ it 'th has a class attached' do
102
+ doc.at_xpath('//th')[:class].should include 'th'
103
+ end
104
+
105
+ it 'tbody has a class attached' do
106
+ doc.at_xpath('//tbody')[:class].should include 'tbody'
107
+ end
108
+
109
+ it 'tr has a class attached' do
110
+ doc.at_xpath('//tr')[:class].should include 'tr'
111
+ end
112
+
113
+ it 'td has a class attached' do
114
+ doc.at_xpath('//td')[:class].should include 'td'
115
+ end
116
+ end
117
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,12 @@
1
+ require 'table_cloth'
2
+ require 'awesome_print'
3
+ require 'nokogiri'
4
+ require 'pry'
5
+
6
+ Dir['./spec/support/**/*.rb'].each {|f| require f }
7
+
8
+ ActionView::Base.send :include, TableClothViewMocks
9
+
1
10
  RSpec.configure do |config|
2
11
  config.treat_symbols_as_metadata_keys_with_true_values = true
3
12
  config.run_all_when_everything_filtered = true
@@ -0,0 +1,5 @@
1
+ class DummyModel < Struct.new(:name, :email, :admin, :id)
2
+ def admin?
3
+ !!admin
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ class DummyTable < TableCloth::Base
2
+ column :id, if: :admin?
3
+ column :name
4
+ column :email
5
+
6
+ def admin?
7
+ view.admin?
8
+ end
9
+ end
10
+
11
+ class DummyTableUnlessAdmin < TableCloth::Base
12
+ column :id, unless: :admin?
13
+ column :name
14
+ column :email
15
+ end
@@ -0,0 +1,5 @@
1
+ module TableClothViewMocks
2
+ def admin?
3
+ true
4
+ end
5
+ end
data/table_cloth.gemspec CHANGED
@@ -18,5 +18,10 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
 
20
20
  gem.add_development_dependency('rspec', '~> 2.11')
21
+ gem.add_development_dependency('awesome_print')
22
+ gem.add_development_dependency('nokogiri')
23
+ gem.add_development_dependency('pry')
24
+ gem.add_development_dependency('guard-rspec')
25
+
21
26
  gem.add_dependency('actionpack', '~> 3.2')
22
27
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: table_cloth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.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: 2012-09-27 00:00:00.000000000 Z
12
+ date: 2012-10-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -27,6 +27,70 @@ dependencies:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
29
  version: '2.11'
30
+ - !ruby/object:Gem::Dependency
31
+ name: awesome_print
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: nokogiri
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: pry
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: guard-rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
30
94
  - !ruby/object:Gem::Dependency
31
95
  name: actionpack
32
96
  requirement: !ruby/object:Gem::Requirement
@@ -53,14 +117,35 @@ files:
53
117
  - .gitignore
54
118
  - .rspec
55
119
  - Gemfile
120
+ - Guardfile
56
121
  - LICENSE.txt
57
122
  - README.md
58
123
  - Rakefile
124
+ - lib/generators/table_generator.rb
125
+ - lib/generators/templates/table.rb
59
126
  - lib/table_cloth.rb
127
+ - lib/table_cloth/action.rb
128
+ - lib/table_cloth/action_view_extension.rb
129
+ - lib/table_cloth/base.rb
130
+ - lib/table_cloth/builder.rb
131
+ - lib/table_cloth/column.rb
132
+ - lib/table_cloth/columns/action.rb
60
133
  - lib/table_cloth/configuration.rb
134
+ - lib/table_cloth/presenter.rb
135
+ - lib/table_cloth/presenters/default.rb
61
136
  - lib/table_cloth/version.rb
137
+ - spec/lib/action_view_extension_spec.rb
138
+ - spec/lib/base_spec.rb
139
+ - spec/lib/builder_spec.rb
140
+ - spec/lib/column_spec.rb
141
+ - spec/lib/columns/action_spec.rb
62
142
  - spec/lib/configuration_spec.rb
143
+ - spec/lib/presenter_spec.rb
144
+ - spec/lib/presenters/default_spec.rb
63
145
  - spec/spec_helper.rb
146
+ - spec/support/dummy_model.rb
147
+ - spec/support/dummy_table.rb
148
+ - spec/support/view_mocks.rb
64
149
  - table_cloth.gemspec
65
150
  homepage: http://www.github.com/bobbytables/table_cloth
66
151
  licenses: []
@@ -88,5 +173,15 @@ specification_version: 3
88
173
  summary: Table Cloth provides an easy and intuitive DSL for creating tables in rails
89
174
  views.
90
175
  test_files:
176
+ - spec/lib/action_view_extension_spec.rb
177
+ - spec/lib/base_spec.rb
178
+ - spec/lib/builder_spec.rb
179
+ - spec/lib/column_spec.rb
180
+ - spec/lib/columns/action_spec.rb
91
181
  - spec/lib/configuration_spec.rb
182
+ - spec/lib/presenter_spec.rb
183
+ - spec/lib/presenters/default_spec.rb
92
184
  - spec/spec_helper.rb
185
+ - spec/support/dummy_model.rb
186
+ - spec/support/dummy_table.rb
187
+ - spec/support/view_mocks.rb