tabletastic 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -4,46 +4,189 @@ Inspired by the projects table_builder and formtastic,
4
4
  I realized how often I created tables for my active record collections.
5
5
  This is my attempt to simply this (the default scaffold):
6
6
 
7
- <table>
7
+ <table>
8
+ <tr>
9
+ <th>Title</th>
10
+ <th>Body</th>
11
+ <th>Author Id</th>
12
+ </tr>
13
+ <% for post in @posts %>
8
14
  <tr>
9
- <th>Title</th>
10
- <th>Body</th>
11
- <th>Author Id</th>
12
- </tr>
13
- <% for post in @posts %>
14
- <tr>
15
- <td><%=h post.title %></td>
16
- <td><%=h post.body %></td>
17
- <td><%=h post.author_id %></td>
18
- <td><%=h author.name %></td>
19
- <td><%= link_to "Show", post %></td>
20
- <td><%= link_to "Edit", edit_post_path(post) %></td>
21
- <td><%= link_to "Destroy", post, :confirm => 'Are you sure?', :method => :delete %></td>
22
- </tr>
23
- <% end %>
24
- </table>
15
+ <td><%=h post.title %></td>
16
+ <td><%=h post.body %></td>
17
+ <td><%=h post.author_id %></td>
18
+ <td><%= link_to "Show", post %></td>
19
+ <td><%= link_to "Edit", edit_post_path(post) %></td>
20
+ <td><%= link_to "Destroy", post, :confirm => 'Are you sure?', :method => :delete %></td>
21
+ </tr>
22
+ <% end %>
23
+ </table>
25
24
 
26
25
  into this:
27
26
 
28
- <% table_for(@posts) do |t| %>
29
- <%= t.data :title, :body, :author %>
30
- <% end %>
27
+ <% table_for(@posts) do |t| %>
28
+ <%= t.data :title, :body, :author %>
29
+ <% end %>
30
+
31
+ and still output the same effective results, but with all the semantic
32
+ goodness that tabular data should have, i.e. a +<thead>+ and +<tbody>+ element.
31
33
 
32
34
 
33
35
  == Warning
34
- THIS PROJECT IS UNDER HEAVY DEVELOPMENT.
35
- IT IS NOT RECOMMENDED FOR USE IN PRODUCTION APPLICATIONS
36
36
 
37
+ This project is still being actively developed. As such, future updates might not be backwards-compatible.
38
+
39
+ == Installation
40
+
41
+ In your Rails project, as a gem:
42
+ config.gem "tabletastic", :source => "http://gemcutter.org"
43
+
44
+ Or, for if you're behind the times, as a plugin:
45
+ script/plugin install git://github.com/jgdavey/tabletastic.git
46
+
47
+
48
+ == Usage
49
+
50
+ By default, you can just use the table_for method to build up your table.
51
+ Assuming you have a Post model with title and body, that belongs to an Author model with a name,
52
+ you can just use the helper. It will try to detect all content fields and belongs to associations.
53
+
54
+ In your view, simply calling:
55
+
56
+ <% table_for(@posts) do |t| %>
57
+ <%= t.data %>
58
+ <% end %>
59
+
60
+ will produce html like this:
61
+
62
+ <table id="posts">
63
+ <thead>
64
+ <tr>
65
+ <th>Title</th>
66
+ <th>Body</th>
67
+ <th>Author</th>
68
+ </tr>
69
+ </thead>
70
+ <tbody>
71
+ <tr class="post odd" id="post_1">
72
+ <td>Something</td>
73
+ <td>Lorem ipsum dolor sit amet consequat. Duis aute irure dolor.</td>
74
+ <td>Jim Beam</td>
75
+ </tr>
76
+ <tr class="post even" id="post_2">
77
+ <td>Second Post</td>
78
+ <td>This is the second post</td>
79
+ <td>Jack Daniels</td>
80
+ </tr>
81
+ <tr class="post odd" id="post_3">
82
+ <td>Something else</td>
83
+ <td>Blah!</td>
84
+ <td></td>
85
+ </tr>
86
+ </tbody>
87
+ </table>
88
+
89
+
90
+ To limit the fields, change the order, or to include fields that are excluded by default (such as created_at),
91
+ You can list methods to call on each resource:
92
+
93
+ <% table_for(@posts) do |t| %>
94
+ <%= t.data :author, :title, :created_at %>
95
+ <% end %>
96
+
97
+ will produce html like:
98
+
99
+ <table id="posts">
100
+ <thead>
101
+ <tr>
102
+ <th>Author</th>
103
+ <th>Title</th>
104
+ <th>Created at</th>
105
+ </tr>
106
+ </thead>
107
+ <tbody>
108
+ <tr id="post_1" class="post odd">
109
+ <td>Jim Beam</td>
110
+ <td>Something</td>
111
+ <td>2009-11-15 02:42:48 UTC</td>
112
+ </tr>
113
+ <tr id="post_2" class="post even">
114
+ <td>Jack Daniels</td>
115
+ <td>Second Post</td>
116
+ <td>2009-11-16 00:11:00 UTC</td>
117
+ </tr>
118
+ <tr id="post_3" class="post odd">
119
+ <td></td>
120
+ <td>Something else</td>
121
+ <td>2009-11-16 00:11:30 UTC</td>
122
+ </tr>
123
+ </tbody>
124
+ </table>
125
+
126
+ For even greater flexibility, you can pass +data+ a block:
127
+
128
+ <% table_for(@posts) do |t| %>
129
+ <% t.data do %>
130
+ <%= t.cell(:title, :cell_html => {:class => "titlestring"}) %>
131
+ <%= t.cell(:body, :heading => "Content") {|p| truncate(p.body, 30)} %>
132
+ <%= t.cell(:author) {|p| p.author && link_to(p.author.name, p.author) } %>
133
+ <%= t.cell(:edit, :heading => '') {|p| link_to "Edit", edit_post_path(p) } %>
134
+ <% end -%>
135
+ <% end %>
136
+
137
+ will product html like:
138
+
139
+ <table id="posts">
140
+ <thead>
141
+ <tr>
142
+ <th>Title</th>
143
+ <th>Content</th>
144
+ <th>Author</th>
145
+ <th></th>
146
+ </tr>
147
+ </thead>
148
+ <tbody>
149
+ <tr class="post odd" id="post_1">
150
+ <td class="titlestring">Something</td>
151
+ <td>Lorem ipsum dolor sit amet,...</td>
152
+ <td>
153
+ <a href="/authors/1">Jim Bean</a>
154
+ </td>
155
+ <td>
156
+ <a href="/posts/1/edit">Edit</a>
157
+ </td>
158
+ </tr>
159
+ <tr class="post even" id="post_2">
160
+ <td class="titlestring">Second Post</td>
161
+ <td>This is the second post</td>
162
+ <td>
163
+ <a href="/authors/2">Jack Daniels</a>
164
+ </td>
165
+ <td>
166
+ <a href="/posts/2/edit">Edit</a>
167
+ </td>
168
+ </tr>
169
+ <tr class="post odd" id="post_3">
170
+ <td class="titlestring">Something else</td>
171
+ <td>Blah!</td>
172
+ <td></td>
173
+ <td>
174
+ <a href="/posts/3/edit">Edit</a>
175
+ </td>
176
+ </tr>
177
+ </tbody>
178
+ </table>
179
+
180
+
181
+ If it _still_ isn't flexible enough for your needs, it might be time to return to static html/erb.
37
182
 
38
183
  == Note on Patches/Pull Requests
39
184
 
40
185
  * Fork the project.
41
186
  * Make your feature addition or bug fix.
42
- * Add tests for it. This is important so I don't break it in a
43
- future version unintentionally.
187
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
44
188
  * Commit, do not mess with rakefile, version, or history.
45
- (if you want to have your own version, that is fine but
46
- bump version in a commit by itself I can ignore when I pull)
189
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
47
190
  * Send me a pull request. Bonus points for topic branches.
48
191
 
49
192
  == Copyright
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.1.0
data/lib/tabletastic.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Tabletastic
2
2
 
3
+ # returns and outputs a table for the given active record collection
3
4
  def table_for(collection, *args)
4
5
  options = args.extract_options!
5
6
  options[:html] ||= {}
@@ -14,35 +15,64 @@ module Tabletastic
14
15
  end
15
16
 
16
17
  class TableBuilder
17
- @@association_methods = %w[to_label display_name full_name name title username login value to_s]
18
+ @@association_methods = %w[display_name full_name name title username login value to_s]
19
+ attr_accessor :field_labels
18
20
 
19
21
  def initialize(collection, template)
20
22
  @collection, @template = collection, template
21
23
  end
22
24
 
25
+ # builds up the fields that the table will include,
26
+ # returns table head and body with all data
23
27
  def data(*args, &block)
24
28
  if block_given?
25
29
  yield self
26
- @template.concat(headers)
30
+ @template.concat(head)
27
31
  @template.concat(body)
28
32
  else
29
33
  @fields = args unless args.empty?
30
- headers + body
34
+ @field_labels = fields.map { |f| f.to_s.humanize }
35
+ [head, body].join("")
31
36
  end
32
37
  end
33
38
 
34
- def headers
39
+ def cell(*args, &block)
40
+ options = args.extract_options!
41
+ @field_labels ||= []
42
+ @fields ||= []
43
+
44
+ method_or_attribute = args.first.to_sym
45
+
46
+ if cell_html = options.delete(:cell_html)
47
+ @fields << [method_or_attribute, cell_html]
48
+ elsif block_given?
49
+ @fields << block.to_proc
50
+ else
51
+ @fields << method_or_attribute
52
+ end
53
+
54
+ if heading = options.delete(:heading)
55
+ @field_labels << heading
56
+ else
57
+ @field_labels << method_or_attribute.to_s.humanize
58
+ end
59
+
60
+ return "" # Since this will likely be called with <%= erb %>, this suppresses strange output
61
+ end
62
+
63
+ def head
64
+ @field_labels ||= fields
35
65
  content_tag(:thead) do
36
66
  header_row
37
67
  end
38
68
  end
39
69
 
40
70
  def header_row
41
- output = "<tr>"
42
- fields.each do |field|
43
- output += content_tag(:th, field.to_s.humanize)
71
+ content_tag(:tr) do
72
+ @field_labels.inject("") do |result,field|
73
+ result += content_tag(:th, field)
74
+ end
44
75
  end
45
- output += "</tr>"
46
76
  end
47
77
 
48
78
  def body
@@ -61,49 +91,66 @@ module Tabletastic
61
91
  end
62
92
 
63
93
  def tds_for_row(record)
64
- fields.inject("") do |cells, field|
65
- cells += content_tag(:td, cell_for(record, field))
94
+ fields.inject("") do |cells, field_or_array|
95
+ field = field_or_array
96
+ if field_or_array.is_a?(Array)
97
+ field = field_or_array.first
98
+ html_options = field_or_array.last
99
+ end
100
+ cells += content_tag(:td, cell_data(record, field), html_options)
66
101
  end
67
102
  end
68
103
 
69
- def cell_for(record, method_or_attribute)
70
- result = record.send(method_or_attribute)
104
+ def cell_data(record, method_or_attribute_or_proc)
105
+ # Get the attribute or association in question
106
+ result = send_or_call(record, method_or_attribute_or_proc)
107
+ # If we already have a string, just return it
71
108
  return result if result.is_a?(String)
109
+
110
+ # If we don't have a string, its likely an association
111
+ # Try to detect which method to use for stringifying the attribute
72
112
  to_string = detect_string_method(result)
73
113
  result.send(to_string) if to_string
74
114
  end
75
115
 
116
+ def fields
117
+ return @fields if defined?(@fields)
118
+ @fields = @collection.empty? ? [] : active_record_fields_for_object(@collection.first)
119
+ end
120
+
121
+ protected
122
+
76
123
  def detect_string_method(association)
77
124
  @@association_methods.detect { |method| association.respond_to?(method) }
78
125
  end
79
126
 
80
- def cell(method_or_attribute)
81
- @fields ||= []
82
- @fields << method_or_attribute.to_sym
83
- return ""
127
+
128
+ def active_record_fields_for_object(obj)
129
+ # normal content columns
130
+ fields = obj.class.content_columns.map(&:name)
131
+
132
+ # active record associations
133
+ associations = obj.class.reflect_on_all_associations(:belongs_to) if obj.class.respond_to?(:reflect_on_all_associations)
134
+ if associations
135
+ associations = associations.map(&:name)
136
+ fields += associations
137
+ end
138
+
139
+ # remove utility columns by default
140
+ fields -= %w[created_at updated_at created_on updated_on lock_version version]
141
+ fields = fields.map(&:to_sym)
84
142
  end
85
143
 
86
- def fields
87
- return @fields if defined?(@fields)
88
- if @collection.empty?
89
- @fields = []
144
+ def send_or_call(object, duck)
145
+ if duck.is_a?(Proc)
146
+ duck.call(object)
90
147
  else
91
- object = @collection.first
92
- associations = object.class.reflect_on_all_associations(:belongs_to) if object.class.respond_to?(:reflect_on_all_associations)
93
- @fields = object.class.content_columns.map(&:name)
94
- if associations
95
- associations = associations.map(&:name)
96
- @fields += associations
97
- end
98
- @fields -= %w[created_at updated_at created_on updated_on lock_version version]
99
- @fields.map!(&:to_sym)
148
+ object.send(duck)
100
149
  end
101
150
  end
102
151
 
103
- private
104
-
105
- def content_tag(name, content = nil, options = nil, escape = true, &block)
106
- @template.content_tag(name, content, options, escape, &block)
107
- end
152
+ def content_tag(name, content = nil, options = nil, escape = true, &block)
153
+ @template.content_tag(name, content, options, escape, &block)
154
+ end
108
155
  end
109
156
  end
@@ -20,15 +20,15 @@ describe "Tabletastic#table_for" do
20
20
  output_buffer.should have_tag("table")
21
21
  end
22
22
 
23
- context "headers and table body" do
23
+ context "head and table body" do
24
24
  before do
25
25
  table_for([]) do |t|
26
- concat(t.headers)
26
+ concat(t.head)
27
27
  concat(t.body)
28
28
  end
29
29
  end
30
30
 
31
- it "should build a basic table and headers" do
31
+ it "should build a basic table and head" do
32
32
  output_buffer.should have_table_with_tag("thead")
33
33
  end
34
34
 
@@ -69,7 +69,7 @@ describe "Tabletastic#table_for" do
69
69
  output_buffer.should have_tag("table#posts")
70
70
  end
71
71
 
72
- it "should output headers" do
72
+ it "should output head" do
73
73
  output_buffer.should have_table_with_tag("thead")
74
74
  end
75
75
 
@@ -162,6 +162,49 @@ describe "Tabletastic#table_for" do
162
162
  end
163
163
  end
164
164
 
165
+ context "with custom cell options" do
166
+ before do
167
+ table_for(@posts) do |t|
168
+ t.data do
169
+ concat(t.cell(:title, :heading => "FooBar"))
170
+ concat(t.cell(:body, :cell_html => {:class => "batquux"}))
171
+ end
172
+ end
173
+ end
174
+
175
+ it "should change the heading label for :heading option" do
176
+ output_buffer.should have_table_with_tag("th", "FooBar")
177
+ output_buffer.should have_table_with_tag("th", "Body")
178
+ end
179
+
180
+ it "should pass :cell_html to the cell" do
181
+ output_buffer.should have_table_with_tag("td.batquux")
182
+ end
183
+ end
184
+
185
+ context "with custom cell options" do
186
+ before do
187
+ table_for(@posts) do |t|
188
+ t.data do
189
+ concat(t.cell(:title) {|p| link_to p.title, "/" })
190
+ concat(t.cell(:body, :heading => "Content") {|p| p.body })
191
+ end
192
+ end
193
+ end
194
+
195
+ it "accepts a block as a lazy attribute" do
196
+ output_buffer.should have_table_with_tag("th:nth-child(1)", "Title")
197
+ output_buffer.should have_table_with_tag("td:nth-child(1)") do |td|
198
+ td.should have_tag("a", "The title of the post")
199
+ end
200
+ end
201
+
202
+ it "accepts a block as a lazy attribute (2)" do
203
+ output_buffer.should have_table_with_tag("th:nth-child(2)", "Content")
204
+ output_buffer.should have_table_with_tag("td:nth-child(2)", "Lorem ipsum")
205
+ end
206
+ end
207
+
165
208
  context "and normal/association columns" do
166
209
  before do
167
210
  ::Post.stub!(:reflect_on_all_associations).with(:belongs_to).and_return([@mock_reflection_belongs_to_author])
@@ -0,0 +1,56 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{tabletastic}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Joshua Davey"]
12
+ s.date = %q{2009-11-17}
13
+ s.description = %q{A table builder for active record collections that produces semantically rich and accessible markup}
14
+ s.email = %q{josh@joshuadavey.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/tabletastic.rb",
27
+ "rails/init.rb",
28
+ "spec/spec.opts",
29
+ "spec/spec_helper.rb",
30
+ "spec/tabletastic_spec.rb",
31
+ "tabletastic.gemspec"
32
+ ]
33
+ s.homepage = %q{http://github.com/jgdavey/tabletastic}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.3.5}
37
+ s.summary = %q{A smarter table builder for Rails collections}
38
+ s.test_files = [
39
+ "spec/spec_helper.rb",
40
+ "spec/tabletastic_spec.rb"
41
+ ]
42
+
43
+ if s.respond_to? :specification_version then
44
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
48
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
49
+ else
50
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
54
+ end
55
+ end
56
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tabletastic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Davey
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-15 00:00:00 -06:00
12
+ date: 2009-11-17 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -43,6 +43,7 @@ files:
43
43
  - spec/spec.opts
44
44
  - spec/spec_helper.rb
45
45
  - spec/tabletastic_spec.rb
46
+ - tabletastic.gemspec
46
47
  has_rdoc: true
47
48
  homepage: http://github.com/jgdavey/tabletastic
48
49
  licenses: []