tabletastic 0.0.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Joshua Davey
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,51 @@
1
+ = Tabletastic
2
+
3
+ Inspired by the projects table_builder and formtastic,
4
+ I realized how often I created tables for my active record collections.
5
+ This is my attempt to simply this (the default scaffold):
6
+
7
+ <table>
8
+ <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>
25
+
26
+ into this:
27
+
28
+ <% table_for(@posts) do |t| %>
29
+ <%= t.data :title, :body, :author %>
30
+ <% end %>
31
+
32
+
33
+ == Warning
34
+ THIS PROJECT IS UNDER HEAVY DEVELOPMENT.
35
+ IT IS NOT RECOMMENDED FOR USE IN PRODUCTION APPLICATIONS
36
+
37
+
38
+ == Note on Patches/Pull Requests
39
+
40
+ * Fork the project.
41
+ * 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.
44
+ * 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)
47
+ * Send me a pull request. Bonus points for topic branches.
48
+
49
+ == Copyright
50
+
51
+ Copyright (c) 2009 Joshua Davey. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ GEM = "tabletastic"
5
+ LONGDESCRIPTION = %Q{A table builder for active record collections \
6
+ that produces semantically rich and accessible markup}
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |s|
11
+ s.name = GEM
12
+ s.summary = %Q{A smarter table builder for Rails collections}
13
+ s.description = LONGDESCRIPTION
14
+ s.email = "josh@joshuadavey.com"
15
+ s.homepage = "http://github.com/jgdavey/tabletastic"
16
+ s.authors = ["Joshua Davey"]
17
+ s.require_path = 'lib'
18
+
19
+ s.add_development_dependency "rspec", ">= 1.2.9"
20
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
25
+ end
26
+
27
+ require 'spec/rake/spectask'
28
+ Spec::Rake::SpecTask.new(:spec) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.spec_files = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ desc 'Test the tabletastic plugin with specdoc formatting and colors'
34
+ Spec::Rake::SpecTask.new('specdoc') do |t|
35
+ t.spec_files = FileList['spec/**/*_spec.rb']
36
+ t.spec_opts = ["--format specdoc", "-c"]
37
+ end
38
+
39
+ desc 'Test the tabletastic plugin with rcov'
40
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
41
+ spec.libs << 'lib' << 'spec'
42
+ spec.pattern = 'spec/**/*_spec.rb'
43
+ spec.rcov = true
44
+ spec.rcov_opts = ['--exclude', 'spec,Library']
45
+ end
46
+
47
+ task :spec => :check_dependencies
48
+
49
+ task :default => :spec
50
+
51
+ require 'rake/rdoctask'
52
+ Rake::RDocTask.new do |rdoc|
53
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
54
+
55
+ rdoc.rdoc_dir = 'rdoc'
56
+ rdoc.title = "Tabletastic #{version}"
57
+ rdoc.rdoc_files.include('README*')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,109 @@
1
+ module Tabletastic
2
+
3
+ def table_for(collection, *args)
4
+ options = args.extract_options!
5
+ options[:html] ||= {}
6
+ options[:html][:id] ||= get_id_for(collection)
7
+ concat(tag(:table, options[:html], true))
8
+ yield TableBuilder.new(collection, self)
9
+ concat("</table>")
10
+ end
11
+
12
+ def get_id_for(collection)
13
+ !collection.empty? && collection.first.class.to_s.tableize
14
+ end
15
+
16
+ class TableBuilder
17
+ @@association_methods = %w[to_label display_name full_name name title username login value to_s]
18
+
19
+ def initialize(collection, template)
20
+ @collection, @template = collection, template
21
+ end
22
+
23
+ def data(*args, &block)
24
+ if block_given?
25
+ yield self
26
+ @template.concat(headers)
27
+ @template.concat(body)
28
+ else
29
+ @fields = args unless args.empty?
30
+ headers + body
31
+ end
32
+ end
33
+
34
+ def headers
35
+ content_tag(:thead) do
36
+ header_row
37
+ end
38
+ end
39
+
40
+ def header_row
41
+ output = "<tr>"
42
+ fields.each do |field|
43
+ output += content_tag(:th, field.to_s.humanize)
44
+ end
45
+ output += "</tr>"
46
+ end
47
+
48
+ def body
49
+ content_tag(:tbody) do
50
+ body_rows
51
+ end
52
+ end
53
+
54
+ def body_rows
55
+ @collection.inject("") do |rows, record|
56
+ rowclass = @template.cycle("odd","even")
57
+ rows += @template.content_tag_for(:tr, record, :class => rowclass) do
58
+ tds_for_row(record)
59
+ end
60
+ end
61
+ end
62
+
63
+ def tds_for_row(record)
64
+ fields.inject("") do |cells, field|
65
+ cells += content_tag(:td, cell_for(record, field))
66
+ end
67
+ end
68
+
69
+ def cell_for(record, method_or_attribute)
70
+ result = record.send(method_or_attribute)
71
+ return result if result.is_a?(String)
72
+ to_string = detect_string_method(result)
73
+ result.send(to_string) if to_string
74
+ end
75
+
76
+ def detect_string_method(association)
77
+ @@association_methods.detect { |method| association.respond_to?(method) }
78
+ end
79
+
80
+ def cell(method_or_attribute)
81
+ @fields ||= []
82
+ @fields << method_or_attribute.to_sym
83
+ return ""
84
+ end
85
+
86
+ def fields
87
+ return @fields if defined?(@fields)
88
+ if @collection.empty?
89
+ @fields = []
90
+ 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)
100
+ end
101
+ end
102
+
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
108
+ end
109
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ # Include hook code here
2
+ require File.join(File.dirname(__FILE__), *%w[.. lib tabletastic])
3
+ ActionView::Base.send :include, Tabletastic
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --diff
2
+ --color
@@ -0,0 +1,105 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ require 'rubygems'
3
+
4
+ def smart_require(lib_name, gem_name, gem_version = '>= 0.0.0')
5
+ begin
6
+ require lib_name if lib_name
7
+ rescue LoadError
8
+ if gem_name
9
+ gem gem_name, gem_version
10
+ require lib_name if lib_name
11
+ end
12
+ end
13
+ end
14
+
15
+ smart_require 'spec', 'spec', '>= 1.2.8'
16
+ require 'spec/autorun'
17
+ smart_require false, 'rspec-rails', '>= 1.2.7.1'
18
+ smart_require 'hpricot', 'hpricot', '>= 0.6.1'
19
+ smart_require 'rspec_hpricot_matchers', 'rspec_hpricot_matchers', '>= 1.0.0'
20
+ smart_require 'active_support', 'activesupport', '>= 2.3.4'
21
+ smart_require 'action_controller', 'actionpack', '>= 2.3.4'
22
+ smart_require 'action_view', 'actionpack', '>= 2.3.4'
23
+
24
+ Spec::Runner.configure do |config|
25
+ config.include(RspecHpricotMatchers)
26
+ end
27
+
28
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
29
+ require 'tabletastic'
30
+
31
+ module TabletasticSpecHelper
32
+ include ActionView::Helpers::UrlHelper
33
+ include ActionView::Helpers::TagHelper
34
+ include ActionView::Helpers::TextHelper
35
+ include ActionView::Helpers::ActiveRecordHelper
36
+ include ActionView::Helpers::RecordIdentificationHelper
37
+ include ActionView::Helpers::RecordTagHelper
38
+ include ActionView::Helpers::CaptureHelper
39
+ include ActiveSupport
40
+ include ActionController::PolymorphicRoutes
41
+
42
+ def self.included(base)
43
+ base.class_eval do
44
+ attr_accessor :output_buffer
45
+ def protect_against_forgery?
46
+ false
47
+ end
48
+ end
49
+ end
50
+
51
+ module ::RspecHpricotMatchers
52
+ def have_table_with_tag(selector, inner_text_or_options = nil, options = {}, &block)
53
+ HaveTag.new("table", nil, {}) &&
54
+ HaveTag.new(selector, inner_text_or_options, options, &block)
55
+ end
56
+ end
57
+
58
+ class ::Post
59
+ def id
60
+ end
61
+ end
62
+ class ::Author
63
+ end
64
+
65
+ def mock_everything
66
+ def post_path(post); "/posts/#{post.id}"; end
67
+ def edit_post_path(post); "/posts/#{post.id}/edit"; end
68
+
69
+ # Sometimes we need a mock @post object and some Authors for belongs_to
70
+ @post = mock('post')
71
+ @post.stub!(:class).and_return(::Post)
72
+ @post.stub!(:id).and_return(nil)
73
+ @post.stub!(:author)
74
+ ::Post.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
75
+ ::Post.stub!(:human_name).and_return('Post')
76
+
77
+ @fred = mock('user')
78
+ @fred.stub!(:class).and_return(::Author)
79
+ @fred.stub!(:name).and_return('Fred Smith')
80
+ @fred.stub!(:id).and_return(37)
81
+
82
+ ::Author.stub!(:find).and_return([@fred])
83
+ ::Author.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
84
+ ::Author.stub!(:human_name).and_return('Author')
85
+ ::Author.stub!(:reflect_on_association).and_return { |column_name| mock('reflection', :options => {}, :klass => Post, :macro => :has_many) if column_name == :posts }
86
+
87
+ @freds_post = mock('post')
88
+ @freds_post.stub!(:class).and_return(::Post)
89
+ @freds_post.stub!(:title).and_return('Fred\'s Post')
90
+ @freds_post.stub!(:body)
91
+ @freds_post.stub!(:id).and_return(19)
92
+ @freds_post.stub!(:author).and_return(@fred)
93
+ @freds_post.stub!(:author_id).and_return(@fred.id)
94
+ @fred.stub!(:posts).and_return([@freds_post])
95
+ @fred.stub!(:post_ids).and_return([@freds_post.id])
96
+
97
+ @mock_reflection_belongs_to_author = mock('reflection', :options => {}, :name => :author, :klass => ::Author, :macro => :belongs_to)
98
+
99
+ ::Post.stub!(:reflect_on_association).and_return do |column_name|
100
+ @mock_reflection_belongs_to_author if column_name == :author
101
+ end
102
+
103
+ ::Post.stub!(:reflect_on_all_associations).with(:belongs_to).and_return([])
104
+ end
105
+ end
@@ -0,0 +1,206 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ include TabletasticSpecHelper
3
+ include Tabletastic
4
+
5
+
6
+ describe "Tabletastic#table_for" do
7
+
8
+ before do
9
+ @output_buffer = ''
10
+ end
11
+
12
+ describe "basics" do
13
+ it "should start with an empty output buffer" do
14
+ output_buffer.should_not have_tag("table")
15
+ end
16
+
17
+ it "should build a basic table" do
18
+ table_for([]) do |t|
19
+ end
20
+ output_buffer.should have_tag("table")
21
+ end
22
+
23
+ context "headers and table body" do
24
+ before do
25
+ table_for([]) do |t|
26
+ concat(t.headers)
27
+ concat(t.body)
28
+ end
29
+ end
30
+
31
+ it "should build a basic table and headers" do
32
+ output_buffer.should have_table_with_tag("thead")
33
+ end
34
+
35
+ it "should build a basic table and body" do
36
+ output_buffer.should have_table_with_tag("tbody")
37
+ end
38
+ end
39
+
40
+ context "with options" do
41
+ it "should pass along html options" do
42
+ table_for([], :html => {:class => 'special'}) do |t|
43
+ end
44
+ output_buffer.should have_tag("table.special")
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "#data" do
50
+ before do
51
+ mock_everything
52
+ ::Post.stub!(:content_columns).and_return([mock('column', :name => 'title'), mock('column', :name => 'body'), mock('column', :name => 'created_at')])
53
+ @post.stub!(:title).and_return("The title of the post")
54
+ @post.stub!(:body).and_return("Lorem ipsum")
55
+ @post.stub!(:created_at).and_return(Time.now)
56
+ @post.stub!(:id).and_return(2)
57
+ @posts = [@post]
58
+ end
59
+
60
+ context "without a block" do
61
+ context "with no other arguments" do
62
+ before do
63
+ table_for(@posts) do |t|
64
+ concat(t.data)
65
+ end
66
+ end
67
+
68
+ it "should output table with id of the class of the collection" do
69
+ output_buffer.should have_tag("table#posts")
70
+ end
71
+
72
+ it "should output headers" do
73
+ output_buffer.should have_table_with_tag("thead")
74
+ end
75
+
76
+ it "should have a <th> for each attribute" do
77
+ # title and body
78
+ output_buffer.should have_table_with_tag("th", :count => 2)
79
+ end
80
+
81
+ it "should include header for Title" do
82
+ output_buffer.should have_table_with_tag("th", "Title")
83
+ end
84
+
85
+ it "should include header for Body" do
86
+ output_buffer.should have_table_with_tag("th", "Body")
87
+ end
88
+
89
+ it "should output body" do
90
+ output_buffer.should have_table_with_tag("tbody")
91
+ end
92
+
93
+ it "should include a row for each record" do
94
+ output_buffer.should have_table_with_tag("tbody") do |tbody|
95
+ tbody.should have_tag("tr", :count => 1)
96
+ end
97
+ end
98
+
99
+ it "should have data for each field" do
100
+ output_buffer.should have_table_with_tag("td", "The title of the post")
101
+ output_buffer.should have_table_with_tag("td", "Lorem ipsum")
102
+ end
103
+
104
+ it "should include the id for the <tr> for each record" do
105
+ output_buffer.should have_table_with_tag("tr#post_#{@post.id}")
106
+ end
107
+
108
+ it "should cycle row classes" do
109
+ @output_buffer = ""
110
+ @posts = [@post, @post]
111
+ table_for(@posts) do |t|
112
+ concat(t.data)
113
+ end
114
+ output_buffer.should have_table_with_tag("tr.odd")
115
+ output_buffer.should have_table_with_tag("tr.even")
116
+ end
117
+
118
+ context "when collection has associations" do
119
+ it "should handle belongs_to associations" do
120
+ ::Post.stub!(:reflect_on_all_associations).with(:belongs_to).and_return([@mock_reflection_belongs_to_author])
121
+ @posts = [@freds_post]
122
+ @output_buffer = ""
123
+ table_for(@posts) do |t|
124
+ concat(t.data)
125
+ end
126
+ output_buffer.should have_table_with_tag("th", "Author")
127
+ output_buffer.should have_table_with_tag("td", "Fred Smith")
128
+ end
129
+ end
130
+ end
131
+
132
+ context "with a list of attributes" do
133
+ before do
134
+ table_for(@posts) do |t|
135
+ concat(t.data(:title, :created_at))
136
+ end
137
+ end
138
+
139
+ it "should call each method passed in, and only those methods" do
140
+ output_buffer.should have_table_with_tag("th", "Title")
141
+ output_buffer.should have_table_with_tag("th", "Created at")
142
+ output_buffer.should_not have_table_with_tag("th", "Body")
143
+ end
144
+ end
145
+ end
146
+
147
+ context "with a block" do
148
+ context "and normal columns" do
149
+ before do
150
+ table_for(@posts) do |t|
151
+ t.data do
152
+ concat(t.cell(:title))
153
+ concat(t.cell(:body))
154
+ end
155
+ end
156
+ end
157
+
158
+ it "should include the data for the fields passed in" do
159
+ output_buffer.should have_table_with_tag("th", "Title")
160
+ output_buffer.should have_tag("td", "The title of the post")
161
+ output_buffer.should have_tag("td", "Lorem ipsum")
162
+ end
163
+ end
164
+
165
+ context "and normal/association columns" do
166
+ before do
167
+ ::Post.stub!(:reflect_on_all_associations).with(:belongs_to).and_return([@mock_reflection_belongs_to_author])
168
+ @posts = [@freds_post]
169
+ table_for(@posts) do |t|
170
+ t.data do
171
+ concat(t.cell(:title))
172
+ concat(t.cell(:author))
173
+ end
174
+ end
175
+ end
176
+
177
+ it "should include normal columns" do
178
+ output_buffer.should have_table_with_tag("th:nth-child(1)", "Title")
179
+ output_buffer.should have_table_with_tag("td:nth-child(1)", "Fred's Post")
180
+ end
181
+
182
+ it "should include belongs_to associations" do
183
+ output_buffer.should have_table_with_tag("th:nth-child(2)", "Author")
184
+ output_buffer.should have_table_with_tag("td:nth-child(2)", "Fred Smith")
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ describe TableBuilder do
192
+ before do
193
+ mock_everything
194
+ ::Post.stub!(:content_columns).and_return([mock('column', :name => 'title'), mock('column', :name => 'body'), mock('column', :name => 'created_at')])
195
+ @posts = [@post, Post.new]
196
+ @builder = TableBuilder.new(@posts, nil)
197
+ end
198
+
199
+ it "should detect attributes" do
200
+ @builder.fields.should include(:title)
201
+ end
202
+
203
+ it "should reject marked attributes" do
204
+ @builder.fields.should_not include(:created_at)
205
+ end
206
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tabletastic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joshua Davey
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-15 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.9
24
+ version:
25
+ description: A table builder for active record collections that produces semantically rich and accessible markup
26
+ email: josh@joshuadavey.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - lib/tabletastic.rb
42
+ - rails/init.rb
43
+ - spec/spec.opts
44
+ - spec/spec_helper.rb
45
+ - spec/tabletastic_spec.rb
46
+ has_rdoc: true
47
+ homepage: http://github.com/jgdavey/tabletastic
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.3.5
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: A smarter table builder for Rails collections
74
+ test_files:
75
+ - spec/spec_helper.rb
76
+ - spec/tabletastic_spec.rb