Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

CSV is now customizable

  • Loading branch information...
commit 7097470b28d05d2d229bb29438114a4862879c24 1 parent ccd8bf5
@pcreux pcreux authored
View
1  Gemfile
@@ -30,6 +30,7 @@ gem 'formtastic', '>= 1.1.0'
gem 'will_paginate', '>= 3.0.pre2'
gem 'inherited_resources'
gem 'sass', '>= 3.1.0'
+gem 'fastercsv', :platforms => :ruby_18
group :development, :test do
gem 'sqlite3-ruby', :require => 'sqlite3'
View
9 README.rdoc
@@ -291,6 +291,15 @@ the collection as a proc to be called at render time.
# Will call available
filter :author, :as => :check_boxes, :collection => proc { Author.all }
+== Customizing the CSV format
+
+Customizing the CSV format is as simple as customizing the index page.
+
+ csv do
+ column :name
+ column("Author") { |post| post.author.full_name }
+ end
+
== Customizing the Form
Active Admin gives complete control over the output of the form by creating a thin DSL on top of
View
2  features/comments/commenting.feature
@@ -81,5 +81,5 @@ Feature: Commenting
Scenario: Viewing all commments for a namespace
When I add a comment "Hello from Comment"
When I am on the index page for comments
- Then I should see a table header for "Body"
+ Then I should see a table header with "Body"
And I should see "Hello from Comment"
View
20 features/index/format_as_csv.feature
@@ -9,10 +9,24 @@ Feature: Format as CSV
When I am on the index page for posts
And I follow "CSV"
Then I should see the CSV:
- | ID | Title | Body | Published At | Created At | Updated At |
+ | Id | Title | Body | Published At | Created At | Updated At |
| \d+ | Hello World | | | (.*) | (.*) |
- Scenario: With index customization
-
Scenario: With CSV format customization
+ Given an index configuration of:
+ """
+ ActiveAdmin.register Post do
+ csv do
+ column :title
+ column("Last update") { |post| post.updated_at }
+ column("Copyright") { "Greg Bell" }
+ end
+ end
+ """
+ And a post with the title "Hello World" exists
+ When I am on the index page for posts
+ And I follow "CSV"
+ Then I should see the CSV:
+ | Title | Last update | Copyright |
+ | Hello World | (.*) | Greg Bell |
View
26 features/step_definitions/format_steps.rb
@@ -6,14 +6,24 @@
Then %{I should see "#{format_type}" within "#index_footer a"}
end
-require 'csv'
-
Then /^I should see the CSV:$/ do |table|
- puts page.body
- csv = CSV.parse(page.body)
- csv.each_with_index do |row, row_index|
- row.each_with_index do |cell, col_index|
- cell.should match(/#{table.raw[row_index][col_index]}/)
- end
+ begin
+ csv = CSV.parse(page.body)
+ csv.each_with_index do |row, row_index|
+ row.each_with_index do |cell, col_index|
+ expected_cell = table.raw.try(:[], row_index).try(:[], col_index)
+ if expected_cell.blank?
+ cell.should be_nil
+ else
+ cell.should match(/#{expected_cell}/)
+ end
+ end
+ end
+ rescue
+ puts "Expecting:"
+ p table.raw
+ puts "to match:"
+ p csv
+ raise $!
end
end
View
2  lib/active_admin.rb
@@ -13,6 +13,7 @@ module ActiveAdmin
autoload :Callbacks, 'active_admin/callbacks'
autoload :Component, 'active_admin/component'
autoload :ControllerAction, 'active_admin/controller_action'
+ autoload :CSVBuilder, 'active_admin/csv_builder'
autoload :Dashboards, 'active_admin/dashboards'
autoload :Devise, 'active_admin/devise'
autoload :DSL, 'active_admin/dsl'
@@ -29,7 +30,6 @@ module ActiveAdmin
autoload :Scope, 'active_admin/scope'
autoload :Sidebar, 'active_admin/sidebar'
autoload :TableBuilder, 'active_admin/table_builder'
- autoload :TableToCSV, 'active_admin/table_to_csv'
autoload :ViewFactory, 'active_admin/view_factory'
autoload :ViewHelpers, 'active_admin/view_helpers'
autoload :Views, 'active_admin/views'
View
45 lib/active_admin/csv_builder.rb
@@ -0,0 +1,45 @@
+module ActiveAdmin
+ # CSVBuilder stores CSV configuration
+ #
+ # Usage example:
+ #
+ # csv_builder = CSVBuilder.new
+ # csv_builder.column :id
+ # csv_builder.column("Name") { |resource| resource.full_name }
+ #
+ class CSVBuilder
+
+ # Return a default CSVBuilder for a resource
+ # The CSVBuilder's columns would be Id followed by this
+ # resource's content columns
+ def self.default_for_resource(resource)
+ new.tap do |csv_builder|
+ csv_builder.column(:id)
+ resource.content_columns.each do |content_column|
+ csv_builder.column(content_column.name.to_sym)
+ end
+ end
+ end
+
+ attr_reader :columns
+
+ def initialize(&block)
+ @columns = []
+ instance_eval &block if block_given?
+ end
+
+ # Add a column
+ def column(name, &block)
+ @columns << Column.new(name, block)
+ end
+
+ class Column
+ attr_reader :name, :data
+
+ def initialize(name, block = nil)
+ @name = name.is_a?(Symbol) ? name.to_s.titleize : name
+ @data = block || name.to_sym
+ end
+ end
+ end
+end
View
13 lib/active_admin/dsl.rb
@@ -115,6 +115,19 @@ def form(options = {}, &block)
controller.form_config = options
end
+ # Configure the CSV format
+ #
+ # For example:
+ #
+ # csv do
+ # column :name
+ # column("Author") { |post| post.author.full_name }
+ # end
+ #
+ def csv(&block)
+ config.csv_builder = CSVBuilder.new(&block)
+ end
+
# Member Actions give you the functionality of defining both the
# action and the route directly from your ActiveAdmin registration
# block.
View
14 lib/active_admin/resource.rb
@@ -46,6 +46,8 @@ class Resource
# Set to false to turn off admin notes
attr_accessor :admin_notes
+ # Set the configuration for the CSV
+ attr_writer :csv_builder
def initialize(namespace, resource, options = {})
@namespace = namespace
@@ -189,6 +191,11 @@ def belongs_to?
!belongs_to_config.nil?
end
+ # The csv builder for this resource
+ def csv_builder
+ @csv_builder || default_csv_builder
+ end
+
private
def default_options
@@ -198,5 +205,8 @@ def default_options
}
end
- end
-end
+ def default_csv_builder
+ @default_csv_builder ||= CSVBuilder.default_for_resource(resource)
+ end
+ end # class Resource
+end # module ActiveAdmin
View
9 lib/active_admin/resource_controller.rb
@@ -21,7 +21,6 @@ class ResourceController < ::InheritedResources::Base
before_filter :only_render_implemented_actions
before_filter :authenticate_active_admin_user
- before_filter :prepare_csv_columns, :only => :index
include ActiveAdmin::ActionItems
include ActionBuilder
@@ -116,13 +115,5 @@ def renderer_for(action)
ActiveAdmin.view_factory["#{action}_page"]
end
helper_method :renderer_for
-
- # Before filter to prepare the columns for CSV. Note this will
- # be deprecated very soon.
- def prepare_csv_columns
- if request.format.csv?
- @csv_columns = resource_class.columns.collect{ |column| column.name.to_sym }
- end
- end
end
end
View
44 lib/active_admin/table_to_csv.rb
@@ -1,44 +0,0 @@
-module ActiveAdmin
-
- # Convert an Arbre::HTML::Table to CSV
- class TableToCSV
- include ActionView::Helpers::SanitizeHelper
-
- def initialize(table)
- @table = table
- end
-
- def to_s
- CSV.generate do |csv|
-
- thead = @table.find_by_tag("thead").first
- csv << thead.find_by_tag("th").collect do |th|
- strip_content th.content
- end
-
- tbody = @table.find_by_tag("tbody").first
- tbody.find_by_tag("tr").each do |tr|
- row = tr.find_by_tag("td").map do |td|
- strip_content td.content
- end
- csv << row
- end
- end
- end
-
- def current_dom_context
- self.to_s
- end
-
- private
-
- def strip_content(html)
- strip_tags(strip_indentation(html))
- end
-
- def strip_indentation(html)
- html.split("\n").map{|string| string.strip }.join("\n")
- end
- end
-
-end
View
17 lib/active_admin/views/pages/index.rb
@@ -26,6 +26,14 @@ def main_content
end
end
+ protected
+
+ def build_scopes
+ if active_admin_config.scopes.any?
+ scopes_renderer active_admin_config.scopes
+ end
+ end
+
# Creates a default configuration for the resource class. This is a table
# with each column displayed as well as all the default actions
def default_index_config
@@ -38,15 +46,6 @@ def default_index_config
end
end
- protected
-
- def build_scopes
- if active_admin_config.scopes.any?
- scopes_renderer active_admin_config.scopes
- end
- end
-
-
# Returns the actual class for renderering the main content on the index
# page. To set this, use the :as option in the page_config block.
def find_index_renderer_class(symbol_or_class)
View
17 lib/active_admin/views/templates/active_admin/resource/index.csv.erb
@@ -1,2 +1,15 @@
-<%= @csv_columns.collect{|c| c.to_s.titleize }.join(",").html_safe %>
-<%= collection.collect{|resource| @csv_columns.collect{|col| "\"#{resource.send(col).to_s.gsub('"', '""')}\""}.join(",") }.join("\n").html_safe %>
+<%-
+ # FasterCSV is CSV in ruby1.9
+ csv_lib = defined?(FasterCSV) ? FasterCSV : CSV
+
+ csv_output = csv_lib.generate do |csv|
+ columns = active_admin_config.csv_builder.columns
+ csv << columns.map(&:name)
+ collection.each do |resource|
+ csv << columns.map do |column|
+ call_method_or_proc_on resource, column.data
+ end
+ end
+ end
+%>
+<%= csv_output %>
View
8 lib/active_admin/views/templates/active_admin_default/index.csv.arb
@@ -1,8 +0,0 @@
-page_config = active_admin_config.page_configs[:index] || ActiveAdmin::PageConfig.new(:as => table) do |display|
- id_column
- resource_class.content_columns.each do |col|
- column col.name.to_sym
- end
-end
-index_table = insert_tag(ActiveAdmin::Views::IndexAsTable, page_config, collection)
-@__current_dom_element__ = ActiveAdmin::TableToCSV.new(index_table)
View
35 spec/controllers/index_as_csv_spec.rb
@@ -1,35 +0,0 @@
-require 'spec_helper'
-
-describe_with_render Admin::PostsController do
- describe "get index with format csv" do
-
- before do
- Post.create :title => "Hello World"
- Post.create :title => "Goodbye World"
- end
-
- it "should return csv" do
- get :index, 'format' => 'csv'
- response.content_type.should == 'text/csv'
- end
-
- it "should return a header and a line for each item" do
- get :index, 'format' => 'csv'
- response.body.split("\n").size.should == 3
- end
-
- Post.columns.each do |column|
- it "should include a header for #{column.name}" do
- get :index, 'format' => 'csv'
- response.body.split("\n").first.should include(column.name.titleize)
- end
- end
-
- it "should set a much higher per page pagination" do
- 100.times{ Post.create :title => "woot" }
- get :index, 'format' => 'csv'
- response.body.split("\n").size.should == 103
- end
-
- end
-end
View
83 spec/unit/csv_builder_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe ActiveAdmin::CSVBuilder do
+
+ describe '.default_for_resource using Post' do
+ let(:csv_builder) { ActiveAdmin::CSVBuilder.default_for_resource(Post) }
+
+ it "should return a default csv_builder for Post" do
+ csv_builder.should be_a(ActiveAdmin::CSVBuilder)
+ end
+
+ specify "the first column should be Id" do
+ csv_builder.columns.first.name.should == 'Id'
+ csv_builder.columns.first.data.should == :id
+ end
+
+ specify "the following columns should be content_column" do
+ csv_builder.columns[1..-1].each_with_index do |column, index|
+ column.name.should == Post.content_columns[index].name.titleize
+ column.data.should == Post.content_columns[index].name.to_sym
+ end
+ end
+ end
+
+ context 'when empty' do
+ let(:builder){ ActiveAdmin::CSVBuilder.new }
+
+ it "should have no columns" do
+ builder.columns.should == []
+ end
+ end
+
+ context "with a symbol column (:title)" do
+ let(:builder) do
+ ActiveAdmin::CSVBuilder.new do
+ column :title
+ end
+ end
+
+ it "should have one colum" do
+ builder.columns.size.should == 1
+ end
+
+ describe "the column" do
+ let(:column){ builder.columns.first }
+
+ it "should have a name of 'Title'" do
+ column.name.should == "Title"
+ end
+
+ it "should have the data :title" do
+ column.data.should == :title
+ end
+ end
+ end
+
+ context "with a block and title" do
+ let(:builder) do
+ ActiveAdmin::CSVBuilder.new do
+ column "My title" do
+ # nothing
+ end
+ end
+ end
+
+ it "should have one colum" do
+ builder.columns.size.should == 1
+ end
+
+ describe "the column" do
+ let(:column){ builder.columns.first }
+
+ it "should have a name of 'My title'" do
+ column.name.should == "My title"
+ end
+
+ it "should have the data :title" do
+ column.data.should be_an_instance_of(Proc)
+ end
+ end
+ end
+
+end
View
16 spec/unit/resource_spec.rb
@@ -248,6 +248,22 @@ module ::Mock; class Resource; end; end
config.get_scope_by_id(:published).name.should == "Published"
end
end
+
+ describe "#csv_builder" do
+ context "when no csv builder set" do
+ it "should return a default column builder with id and content columns" do
+ config.csv_builder.columns.size.should == Category.content_columns.size + 1
+ end
+ end
+
+ context "when csv builder set" do
+ it "shuld return the csv_builder we set" do
+ csv_builder = CSVBuilder.new
+ config.csv_builder = csv_builder
+ config.csv_builder.should == csv_builder
+ end
+ end
+ end
describe "admin notes" do
context "when not set" do
View
49 spec/unit/table_to_csv_spec.rb
@@ -1,49 +0,0 @@
-require 'spec_helper'
-
-describe ActiveAdmin::TableToCSV do
- include Arbre::HTML
-
- let(:assigns){ {} }
- let(:helpers){ action_view }
-
- let(:post_1){ Post.new(:title => "Hello world")}
- let(:post_2){ Post.new(:title => "Hello world 2")}
-
- let(:csv){ ActiveAdmin::TableToCSV.new(table).to_s }
-
-
- describe "a basic table" do
- let :table do
- table_for [post_1, post_2] do
- column :title
- column :body
- end
- end
-
-
- it "should render the table headers" do
- csv.split("\n").first.should == "Title,Body"
- end
-
- it "should render the first row" do
- csv.split("\n")[1].should == %{Hello world,""}
- end
- end
-
- describe "a table with html" do
- let :table do
- table_for [post_1, post_2] do
- column("<b>Title</b>".html_safe){|p| a p.title, :href => "/woot" }
- column(:body){|p| para p.body }
- end
- end
-
- it "should render content without html tags" do
- csv.split("\n")[1].should == %{Hello world,""}
- end
- it "should render headers without html tags" do
- csv.split("\n")[0].should == %{Title,Body}
- end
- end
-
-end
Please sign in to comment.
Something went wrong with that request. Please try again.