tabletastic 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/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: []