table_cloth 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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