tabletastic 0.1.0 → 0.1.1

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
@@ -126,11 +126,10 @@ will produce html like:
126
126
  For even greater flexibility, you can pass +data+ a block:
127
127
 
128
128
  <% table_for(@posts) do |t| %>
129
- <% t.data do %>
129
+ <% t.data :actions => :all do %>
130
130
  <%= t.cell(:title, :cell_html => {:class => "titlestring"}) %>
131
131
  <%= t.cell(:body, :heading => "Content") {|p| truncate(p.body, 30)} %>
132
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
133
  <% end -%>
135
134
  <% end %>
136
135
 
@@ -143,6 +142,8 @@ will product html like:
143
142
  <th>Content</th>
144
143
  <th>Author</th>
145
144
  <th></th>
145
+ <th></th>
146
+ <th></th>
146
147
  </tr>
147
148
  </thead>
148
149
  <tbody>
@@ -152,9 +153,15 @@ will product html like:
152
153
  <td>
153
154
  <a href="/authors/1">Jim Bean</a>
154
155
  </td>
155
- <td>
156
+ <td class="actions show_link">
157
+ <a href="/posts/1">Show</a>
158
+ </td>
159
+ <td class="actions edit_link">
156
160
  <a href="/posts/1/edit">Edit</a>
157
161
  </td>
162
+ <td class="actions destroy_link">
163
+ <a href="/posts/1/edit">Destroy</a> <!-- inline javascript omitted -->
164
+ </td>
158
165
  </tr>
159
166
  <tr class="post even" id="post_2">
160
167
  <td class="titlestring">Second Post</td>
@@ -162,17 +169,29 @@ will product html like:
162
169
  <td>
163
170
  <a href="/authors/2">Jack Daniels</a>
164
171
  </td>
165
- <td>
172
+ <td class="actions show_link">
173
+ <a href="/posts/2">Show</a>
174
+ </td>
175
+ <td class="actions edit_link">
166
176
  <a href="/posts/2/edit">Edit</a>
167
177
  </td>
178
+ <td class="actions destroy_link">
179
+ <a href="/posts/2/edit">Destroy</a> <!-- inline javascript omitted -->
180
+ </td>
168
181
  </tr>
169
182
  <tr class="post odd" id="post_3">
170
183
  <td class="titlestring">Something else</td>
171
184
  <td>Blah!</td>
172
185
  <td></td>
173
- <td>
186
+ <td class="actions show_link">
187
+ <a href="/posts/3">Show</a>
188
+ </td>
189
+ <td class="actions edit_link">
174
190
  <a href="/posts/3/edit">Edit</a>
175
191
  </td>
192
+ <td class="actions destroy_link">
193
+ <a href="/posts/3/edit">Destroy</a> <!-- inline javascript omitted -->
194
+ </td>
176
195
  </tr>
177
196
  </tbody>
178
197
  </table>
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
data/lib/tabletastic.rb CHANGED
@@ -2,40 +2,85 @@ module Tabletastic
2
2
 
3
3
  # returns and outputs a table for the given active record collection
4
4
  def table_for(collection, *args)
5
+ klass = default_class_for(collection)
5
6
  options = args.extract_options!
6
7
  options[:html] ||= {}
7
- options[:html][:id] ||= get_id_for(collection)
8
+ options[:html][:id] ||= get_id_for(klass)
8
9
  concat(tag(:table, options[:html], true))
9
- yield TableBuilder.new(collection, self)
10
+ yield TableBuilder.new(collection, klass, self)
10
11
  concat("</table>")
11
12
  end
12
13
 
13
- def get_id_for(collection)
14
- !collection.empty? && collection.first.class.to_s.tableize
14
+ private
15
+ # Finds the class representing the objects within the collection
16
+ def default_class_for(collection)
17
+ if collection.respond_to?(:proxy_reflection)
18
+ collection.proxy_reflection.klass
19
+ elsif !collection.empty?
20
+ collection.first.class
21
+ end
22
+ end
23
+
24
+ def get_id_for(klass)
25
+ klass.to_s.tableize
15
26
  end
16
27
 
17
28
  class TableBuilder
18
29
  @@association_methods = %w[display_name full_name name title username login value to_s]
19
30
  attr_accessor :field_labels
31
+ attr_reader :collection, :klass
20
32
 
21
- def initialize(collection, template)
22
- @collection, @template = collection, template
33
+ def initialize(collection, klass, template)
34
+ @collection, @klass, @template = collection, klass, template
23
35
  end
24
36
 
25
37
  # builds up the fields that the table will include,
26
38
  # returns table head and body with all data
27
- def data(*args, &block)
39
+ #
40
+ # Can be used one of three ways:
41
+ #
42
+ # * Alone, which will try to detect all content columns on the resource
43
+ # * With an array of methods to call on each element in the collection
44
+ # * With a block, which assumes you will use +cell+ method to build up
45
+ # the table
46
+ #
47
+ #
48
+ def data(*args, &block) # :yields: tablebody
49
+ options = args.extract_options!
28
50
  if block_given?
29
51
  yield self
52
+ action_cells(options[:actions])
30
53
  @template.concat(head)
31
54
  @template.concat(body)
32
55
  else
33
- @fields = args unless args.empty?
56
+ @fields = args.empty? ? fields : args
34
57
  @field_labels = fields.map { |f| f.to_s.humanize }
58
+ action_cells(options[:actions])
35
59
  [head, body].join("")
36
60
  end
37
61
  end
38
62
 
63
+ # individually specify a column, which will build up the header,
64
+ # and method or block to call on each resource in the array
65
+ #
66
+ # Should always be called within the block of +data+
67
+ #
68
+ # For example:
69
+ #
70
+ # t.cell :blah
71
+ #
72
+ # will simply call +blah+ on each resource
73
+ #
74
+ # You can also provide a block, which allows for other helpers
75
+ # or custom formatting. Since by default erb will just call +to_s+
76
+ # on an any element output, you can more greatly control the output:
77
+ #
78
+ # t.cell(:price) {|resource| number_to_currency(resource)}
79
+ #
80
+ # would output something like:
81
+ #
82
+ # <td>$1.50</td>
83
+ #
39
84
  def cell(*args, &block)
40
85
  options = args.extract_options!
41
86
  @field_labels ||= []
@@ -43,12 +88,16 @@ module Tabletastic
43
88
 
44
89
  method_or_attribute = args.first.to_sym
45
90
 
91
+ method_or_attribute_or_proc = if block_given?
92
+ block.to_proc
93
+ else
94
+ method_or_attribute
95
+ end
96
+
46
97
  if cell_html = options.delete(:cell_html)
47
- @fields << [method_or_attribute, cell_html]
48
- elsif block_given?
49
- @fields << block.to_proc
98
+ @fields << [method_or_attribute_or_proc, cell_html]
50
99
  else
51
- @fields << method_or_attribute
100
+ @fields << method_or_attribute_or_proc
52
101
  end
53
102
 
54
103
  if heading = options.delete(:heading)
@@ -56,8 +105,9 @@ module Tabletastic
56
105
  else
57
106
  @field_labels << method_or_attribute.to_s.humanize
58
107
  end
59
-
60
- return "" # Since this will likely be called with <%= erb %>, this suppresses strange output
108
+ # Since this will likely be called with <%= %> (aka 'concat'), explicitly return an empty string
109
+ # This suppresses unwanted output
110
+ return ""
61
111
  end
62
112
 
63
113
  def head
@@ -85,12 +135,12 @@ module Tabletastic
85
135
  @collection.inject("") do |rows, record|
86
136
  rowclass = @template.cycle("odd","even")
87
137
  rows += @template.content_tag_for(:tr, record, :class => rowclass) do
88
- tds_for_row(record)
138
+ cells_for_row(record)
89
139
  end
90
140
  end
91
141
  end
92
142
 
93
- def tds_for_row(record)
143
+ def cells_for_row(record)
94
144
  fields.inject("") do |cells, field_or_array|
95
145
  field = field_or_array
96
146
  if field_or_array.is_a?(Array)
@@ -113,9 +163,34 @@ module Tabletastic
113
163
  result.send(to_string) if to_string
114
164
  end
115
165
 
166
+ # Used internally to build up cells for common CRUD actions
167
+ def action_cells(actions)
168
+ return if actions.blank?
169
+ actions = [actions] if !actions.respond_to?(:each)
170
+ actions = [:show, :edit, :destroy] if actions == [:all]
171
+ actions.each do |action|
172
+ action_link(action.to_sym)
173
+ end
174
+ end
175
+
176
+ # Dynamically builds links for the action
177
+ def action_link(action)
178
+ html_class = "actions #{action.to_s}_link"
179
+ self.cell(action, :heading => "", :cell_html => {:class => html_class}) do |resource|
180
+ case action
181
+ when :show
182
+ @template.link_to("Show", resource)
183
+ when :edit
184
+ @template.link_to("Edit", @template.polymorphic_path(resource, :action => :edit))
185
+ when :destroy
186
+ @template.link_to("Destroy", resource, :method => :delete)
187
+ end
188
+ end
189
+ end
190
+
116
191
  def fields
117
192
  return @fields if defined?(@fields)
118
- @fields = @collection.empty? ? [] : active_record_fields_for_object(@collection.first)
193
+ @fields = @collection.empty? ? [] : active_record_fields
119
194
  end
120
195
 
121
196
  protected
@@ -125,13 +200,14 @@ module Tabletastic
125
200
  end
126
201
 
127
202
 
128
- def active_record_fields_for_object(obj)
203
+ def active_record_fields
204
+ return [] if klass.blank?
129
205
  # normal content columns
130
- fields = obj.class.content_columns.map(&:name)
206
+ fields = klass.content_columns.map(&:name)
131
207
 
132
208
  # 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
209
+ if klass.respond_to?(:reflect_on_all_associations)
210
+ associations = klass.reflect_on_all_associations(:belongs_to)
135
211
  associations = associations.map(&:name)
136
212
  fields += associations
137
213
  end
@@ -141,16 +217,18 @@ module Tabletastic
141
217
  fields = fields.map(&:to_sym)
142
218
  end
143
219
 
220
+ def content_tag(name, content = nil, options = nil, escape = true, &block)
221
+ @template.content_tag(name, content, options, escape, &block)
222
+ end
223
+
224
+ private
225
+
144
226
  def send_or_call(object, duck)
145
- if duck.is_a?(Proc)
227
+ if duck.respond_to?(:call)
146
228
  duck.call(object)
147
229
  else
148
230
  object.send(duck)
149
231
  end
150
232
  end
151
-
152
- def content_tag(name, content = nil, options = nil, escape = true, &block)
153
- @template.content_tag(name, content, options, escape, &block)
154
- end
155
233
  end
156
234
  end
@@ -129,6 +129,57 @@ describe "Tabletastic#table_for" do
129
129
  end
130
130
  end
131
131
 
132
+ context "with options[:actions]" do
133
+ it "includes path to post for :show" do
134
+ table_for(@posts) do |t|
135
+ concat(t.data(:actions => :show))
136
+ end
137
+ output_buffer.should have_table_with_tag("a[@href=/posts/#{@post.id}]")
138
+ output_buffer.should have_table_with_tag("th", "")
139
+ end
140
+
141
+ it "should have a cell with default class 'actions' and the action name" do
142
+ table_for(@posts) do |t|
143
+ concat(t.data(:actions => :show))
144
+ end
145
+ output_buffer.should have_tag("td.actions.show_link") do |td|
146
+ td.should have_tag("a")
147
+ end
148
+ end
149
+
150
+ it "includes path to post for :edit" do
151
+ table_for(@posts) do |t|
152
+ concat(t.data(:actions => :edit))
153
+ end
154
+ output_buffer.should have_tag("a[@href=/posts/#{@post.id}/edit]", "Edit")
155
+ end
156
+
157
+ it "includes path to post for :destroy" do
158
+ table_for(@posts) do |t|
159
+ concat(t.data(:actions => :destroy))
160
+ end
161
+ output_buffer.should have_table_with_tag("a[@href=/posts/#{@post.id}]", "Destroy")
162
+ output_buffer.should have_table_with_tag("th", "")
163
+ end
164
+
165
+ it "includes path to post for :show and :edit" do
166
+ table_for(@posts) do |t|
167
+ concat(t.data(:actions => [:show, :edit]))
168
+ end
169
+ output_buffer.should have_tag("td:nth-child(3) a[@href=/posts/#{@post.id}]", "Show")
170
+ output_buffer.should have_tag("td:nth-child(4) a[@href=/posts/#{@post.id}/edit]", "Edit")
171
+ end
172
+
173
+ it "includes path to post for :all" do
174
+ table_for(@posts) do |t|
175
+ concat(t.data(:actions => :all))
176
+ end
177
+ output_buffer.should have_tag("td:nth-child(3) a[@href=/posts/#{@post.id}]", "Show")
178
+ output_buffer.should have_tag("td:nth-child(4) a[@href=/posts/#{@post.id}/edit]", "Edit")
179
+ output_buffer.should have_tag("td:nth-child(5) a[@href=/posts/#{@post.id}]", "Destroy")
180
+ end
181
+ end
182
+
132
183
  context "with a list of attributes" do
133
184
  before do
134
185
  table_for(@posts) do |t|
@@ -142,6 +193,20 @@ describe "Tabletastic#table_for" do
142
193
  output_buffer.should_not have_table_with_tag("th", "Body")
143
194
  end
144
195
  end
196
+
197
+ context "with a list of attributes and options[:actions]" do
198
+ it "includes path to post for :show" do
199
+ table_for(@posts) do |t|
200
+ concat(t.data(:title, :created_at, :actions => :show))
201
+ end
202
+ output_buffer.should have_tag("th:nth-child(1)", "Title")
203
+ output_buffer.should have_tag("th:nth-child(2)", "Created at")
204
+ output_buffer.should have_tag("th:nth-child(3)", "")
205
+ output_buffer.should_not have_tag("th", "Body")
206
+
207
+ output_buffer.should have_tag("td:nth-child(3) a[@href=/posts/#{@post.id}]")
208
+ end
209
+ end
145
210
  end
146
211
 
147
212
  context "with a block" do
@@ -205,6 +270,18 @@ describe "Tabletastic#table_for" do
205
270
  end
206
271
  end
207
272
 
273
+ context "with options[:actions]" do
274
+ it "includes path to post for :show" do
275
+ table_for(@posts) do |t|
276
+ t.data(:actions => :show) do
277
+ concat(t.cell(:title))
278
+ concat(t.cell(:body))
279
+ end
280
+ end
281
+ output_buffer.should have_table_with_tag("td:nth-child(3) a[@href=/posts/#{@post.id}]")
282
+ end
283
+ end
284
+
208
285
  context "and normal/association columns" do
209
286
  before do
210
287
  ::Post.stub!(:reflect_on_all_associations).with(:belongs_to).and_return([@mock_reflection_belongs_to_author])
@@ -236,7 +313,7 @@ describe TableBuilder do
236
313
  mock_everything
237
314
  ::Post.stub!(:content_columns).and_return([mock('column', :name => 'title'), mock('column', :name => 'body'), mock('column', :name => 'created_at')])
238
315
  @posts = [@post, Post.new]
239
- @builder = TableBuilder.new(@posts, nil)
316
+ @builder = TableBuilder.new(@posts, ::Post, nil)
240
317
  end
241
318
 
242
319
  it "should detect attributes" do
data/tabletastic.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{tabletastic}
8
- s.version = "0.1.0"
8
+ s.version = "0.1.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Joshua Davey"]
12
- s.date = %q{2009-11-17}
12
+ s.date = %q{2009-11-22}
13
13
  s.description = %q{A table builder for active record collections that produces semantically rich and accessible markup}
14
14
  s.email = %q{josh@joshuadavey.com}
15
15
  s.extra_rdoc_files = [
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.1.0
4
+ version: 0.1.1
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-17 00:00:00 -06:00
12
+ date: 2009-11-22 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency