├── .travis.yml ├── .yardopts ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── acts_as_xlsx.gemspec ├── lib ├── acts_as_xlsx.rb └── acts_as_xlsx │ ├── ar.rb │ └── version.rb └── test ├── database.yml ├── helper.rb └── tc_acts_as_xlsx.rb /.travis.yml: -------------------------------------------------------------------------------- 1 | bundler_args: --binstubs 2 | rvm: 3 | - 1.8.7 4 | - 1.9.2 5 | - 1.9.3 6 | 7 | notifications: 8 | irc: "irc.freenode.org#axlsx 9 | 10 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --protected 2 | --no-private 3 | --main README.md 4 | --title "acts_as_xlsx: a rails plug in for axlsx" 5 | - 6 | LICENSE 7 | CHANGELOG.md -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | --------- 3 | 4 | - **December.6.11**: 1.0.3 release 5 | - Added Mime type definition for rails to support respond_to |format| style handling in controllers. 6 | 7 | - **December.3.11**: 1.0.2 release 8 | Added support for chained method columns like :'model.association.attribute' 9 | 10 | - **October.30.11**: 1.0.1 release 11 | - Patch for inclusion error 12 | 13 | - **October.30.11**: 1.0.0 release 14 | - First release 15 | 16 | - **October.30.11**: 1.0.0a release 17 | - First pre release 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Randy Morgan 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Acts as xlsx: Office Open XML Spreadsheet Generation plugin for active record 2 | ==================================== 3 | [![Build Status](https://secure.travis-ci.org/randym/acts_as_xlsx.png)](http://travis-ci.org/randym/acts_as_xlsx/) 4 | 5 | **IRC**: [irc.freenode.net / #axlsx](irc://irc.freenode.net/axlsx) 6 | **Git**: [http://github.com/randym/acts_as_xlsx](http://github.com/randym/acts_as_xlsx) 7 | **Author**: Randy Morgan 8 | **Copyright**: 2011 9 | **License**: MIT License 10 | **Latest Version**: 1.0.6 11 | **Ruby Version**: 1.8.7 - 1.9.3 12 | **Release Date**: July 27th 2012 13 | 14 | Synopsis 15 | -------- 16 | 17 | Acts_as_xlsx is an active record plugin for Axlsx. It makes generating excel spreadsheets from any subclass of ActiveRecord::Base as simple as a couple of lines of code. 18 | 19 | Feature List 20 | ------------ 21 | 22 | **1. Mixes into active record base to provide to_xlsx 23 | 24 | **2. Can work at the end of any series of finder methods. 25 | 26 | **3. Can accept any set of find options 27 | 28 | **4. Automates localization of column heading with i18n support 29 | 30 | **5. Lets you specify columns and methods chains you want to call to populate your table in one go. 31 | 32 | **6. Gives you access to the axlsx package so you can add styles, charts and pictures to satisfy those flashy sales guys. 33 | 34 | **7. Plays nicely with both ruby 1.8.7 + rails 2.3 as well as ruby 1.9.3 + rails 3 35 | 36 | **8. Automatically registers xlsx Mime type for use in respond_to web-service support. 37 | 38 | **9. Allows you to specify the Axlsx package to add your data to so you can create a single workbook with a sheet for each to_xlsx call. 39 | 40 | Installing 41 | ---------- 42 | 43 | To install, use the following command: 44 | 45 | $ gem install acts_as_xlsx 46 | 47 | Usage 48 | ----- 49 | 50 | ###Examples 51 | 52 | See the Guides here: 53 | 54 | [http://axlsx.blogspot.com/] (http://axlsx.blogspot.com/) 55 | 56 | For examples on how to use axlsx for custom styles, charts, images and more see: 57 | 58 | [http://github.com/randym/axlsx](http://github.com/randym/axlsx) 59 | 60 | ###Documentation 61 | 62 | This gem is 100% documented with YARD, an exceptional documentation library. To see documentation for this, and all the gems installed on your system use: 63 | 64 | gem install yard 65 | yard server -g 66 | 67 | 68 | ###Specs 69 | 70 | This gem has 100% coverage using Test::Unit 71 | 72 | Changelog 73 | --------- 74 | - **July.27.12**: 1.0.6 release 75 | - conditionaly register XLSX mime type 76 | 77 | - **February.14.12**: 1.0.5 release 78 | - acts_as_xlsx propery declares it's dependancy on i18n instead of relying on the parent gem. 79 | 80 | - **December.7.11**: 1.0.4 release 81 | - acts_as_xlsx now supports specifying the Axlsx package the export will be added to 82 | - Support for custom named and I18n names for worksheets. 83 | 84 | 85 | Please see the {file:CHANGELOG.md} document for past release information. 86 | 87 | 88 | Copyright 89 | --------- 90 | 91 | Acts_as_xlsx © 2011 by [Randy Morgan](mailto:digial.ipseity@gmail.com). Acts_as_xlsx is 92 | licensed under the MIT license. Please see the {file:LICENSE} document for more information. 93 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/lib/acts_as_xlsx/version.rb') 2 | 3 | task :build => :gendoc do 4 | system "gem build acts_as_xlsx.gemspec" 5 | end 6 | 7 | task :gendoc do 8 | system "yardoc" 9 | end 10 | 11 | task :test do 12 | require 'rake/testtask' 13 | Rake::TestTask.new do |t| 14 | t.libs << 'test' 15 | t.test_files = FileList['test/**/tc_*.rb'] 16 | t.verbose = true 17 | end 18 | end 19 | 20 | task :release => :build do 21 | system "gem push acts_as_xlsx-#{Axlsx::Ar::VERSION}.gem" 22 | end 23 | 24 | task :default => :test -------------------------------------------------------------------------------- /acts_as_xlsx.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/lib/acts_as_xlsx/version.rb') 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'acts_as_xlsx' 5 | s.version = Axlsx::Ar::VERSION 6 | s.author = "Randy Morgan" 7 | s.email = 'digital.ipseity@gmail.com' 8 | s.homepage = 'https://github.com/randym/acts_as_xlsx' 9 | s.platform = Gem::Platform::RUBY 10 | s.date = Time.now.strftime('%Y-%m-%d') 11 | s.summary = "ActiveRecord support for Axlsx" 12 | s.has_rdoc = 'acts_as_xlsx' 13 | s.description = <<-eof 14 | acts_as_xlsx lets you turn any ActiveRecord::Base inheriting class into an excel spreadsheet. 15 | It can be added to any finder method or scope chain and can use localized column and sheet names with I18n. 16 | eof 17 | s.files = Dir.glob("{lib/**/*}") + %w{ LICENSE README.md Rakefile CHANGELOG.md .yardopts } 18 | s.test_files = Dir.glob("{test/**/*}") 19 | 20 | s.add_runtime_dependency 'axlsx', '>= 1.0.13' 21 | s.add_runtime_dependency 'activerecord', '>= 2.3.9' 22 | s.add_runtime_dependency 'i18n', '>= 0.4.1' 23 | 24 | s.add_development_dependency 'rake', "0.8.7" if RUBY_VERSION == "1.9.2" 25 | s.add_development_dependency 'rake', "~> 0.9" if ["1.9.3", "1.8.7"].include?(RUBY_VERSION) 26 | s.add_development_dependency 'bundler' 27 | s.add_development_dependency 'sqlite3', "~> 1.3.5" 28 | s.add_development_dependency 'yard' 29 | s.add_development_dependency 'rdiscount' 30 | 31 | s.required_ruby_version = '>= 1.8.7' 32 | s.require_path = 'lib' 33 | end 34 | -------------------------------------------------------------------------------- /lib/acts_as_xlsx.rb: -------------------------------------------------------------------------------- 1 | require 'acts_as_xlsx/ar.rb' 2 | begin 3 | # The mime type to be used in respond_to |format| style web-services in rails 4 | unless defined? Mime::XLSX 5 | Mime::Type.register "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", :xlsx 6 | end 7 | rescue NameError 8 | puts "Mime module not defined. Skipping registration of xlsx" 9 | end 10 | -------------------------------------------------------------------------------- /lib/acts_as_xlsx/ar.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Axlsx is a gem or generating excel spreadsheets with charts, images and many other features. 3 | # 4 | # acts_as_xlsx provides integration into active_record for Axlsx. 5 | # 6 | require 'axlsx' 7 | 8 | # Adding to the Axlsx module 9 | # @see http://github.com/randym/axlsx 10 | module Axlsx 11 | # === Overview 12 | # This module defines the acts_as_xlsx class method and provides to_xlsx support to both AR classes and instances 13 | module Ar 14 | 15 | def self.included(base) # :nodoc: 16 | base.send :extend, ClassMethods 17 | end 18 | 19 | # Class methods for the mixin 20 | module ClassMethods 21 | 22 | # defines the class method to inject to_xlsx 23 | # @option options [Array, Symbol] columns an array of symbols defining the columns and methods to call in generating sheet data for each row. 24 | # @option options [String] i18n (default nil) The path to search for localization. When this is specified your i18n.t will be used to determine the labels for columns. 25 | # @example 26 | # class MyModel < ActiveRecord::Base 27 | # acts_as_xlsx :columns=> [:id, :created_at, :updated_at], :i18n => 'activerecord.attributes' 28 | def acts_as_xlsx(options={}) 29 | cattr_accessor :xlsx_i18n, :xlsx_columns 30 | self.xlsx_i18n = options.delete(:i18n) || false 31 | self.xlsx_columns = options.delete(:columns) 32 | extend Axlsx::Ar::SingletonMethods 33 | end 34 | end 35 | 36 | # Singleton methods for the mixin 37 | module SingletonMethods 38 | 39 | # Maps the AR class to an Axlsx package 40 | # options are passed into AR find 41 | # @param [Array, Array] columns as an array of symbols or a symbol that defines the attributes or methods to render in the sheet. 42 | # @option options [Integer] header_style to apply to the first row of field names 43 | # @option options [Array, Symbol] types an array of Axlsx types for each cell in data rows or a single type that will be applied to all types. 44 | # @option options [Integer, Array] style The style to pass to Worksheet#add_row 45 | # @option options [String] i18n The path to i18n attributes. (usually activerecord.attributes) 46 | # @option options [Package] package An Axlsx::Package. When this is provided the output will be added to the package as a new sheet. # @option options [String] name This will be used to name the worksheet added to the package. If it is not provided the name of the table name will be humanized when i18n is not specified or the I18n.t for the table name. 47 | # @see Worksheet#add_row 48 | def to_xlsx(options = {}) 49 | if self.xlsx_columns.nil? 50 | self.xlsx_columns = self.column_names.map { |c| c = c.to_sym } 51 | end 52 | 53 | row_style = options.delete(:style) 54 | header_style = options.delete(:header_style) || row_style 55 | types = [options.delete(:types) || []].flatten 56 | 57 | i18n = options.delete(:i18n) || self.xlsx_i18n 58 | columns = options.delete(:columns) || self.xlsx_columns 59 | 60 | p = options.delete(:package) || Package.new 61 | row_style = p.workbook.styles.add_style(row_style) unless row_style.nil? 62 | header_style = p.workbook.styles.add_style(header_style) unless header_style.nil? 63 | i18n = self.xlsx_i18n == true ? 'activerecord.attributes' : i18n 64 | sheet_name = options.delete(:name) || (i18n ? I18n.t("#{i18n}.#{table_name.underscore}") : table_name.humanize) 65 | data = options.delete(:data) || [*find(:all, options)] 66 | data.compact! 67 | data.flatten! 68 | 69 | return p if data.empty? 70 | p.workbook.add_worksheet(:name=>sheet_name) do |sheet| 71 | 72 | col_labels = if i18n 73 | columns.map { |c| I18n.t("#{i18n}.#{self.name.underscore}.#{c}") } 74 | else 75 | columns.map { |c| c.to_s.humanize } 76 | end 77 | 78 | sheet.add_row col_labels, :style=>header_style 79 | 80 | data.each do |r| 81 | row_data = columns.map do |c| 82 | if c.to_s =~ /\./ 83 | v = r; c.to_s.split('.').each { |method| v = v.send(method) }; v 84 | else 85 | r.send(c) 86 | end 87 | end 88 | sheet.add_row row_data, :style=>row_style, :types=>types 89 | end 90 | end 91 | p 92 | end 93 | end 94 | end 95 | end 96 | 97 | require 'active_record' 98 | ActiveRecord::Base.send :include, Axlsx::Ar 99 | 100 | 101 | -------------------------------------------------------------------------------- /lib/acts_as_xlsx/version.rb: -------------------------------------------------------------------------------- 1 | module Axlsx 2 | module Ar 3 | # The current version of the gem 4 | VERSION = "1.0.6" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/database.yml: -------------------------------------------------------------------------------- 1 | sqlite3: 2 | adapter: sqlite3 3 | database: test/acts_as_xlsx.sqlite3.db -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) 2 | ActiveRecord::Base.establish_connection(config['sqlite3']) 3 | ActiveRecord::Schema.define(:version => 0) do 4 | begin 5 | drop_table :author, :force => true 6 | drop_table :authors, :force => true 7 | drop_table :comments, :force => true 8 | drop_table :posts, :force => true 9 | rescue 10 | #dont really care if the tables are not dropped 11 | end 12 | 13 | create_table(:authors, :force => true) do |t| 14 | t.string :name 15 | end 16 | 17 | create_table(:comments, :force => true) do |t| 18 | t.text :content 19 | t.integer :post_id 20 | t.integer :author_id 21 | t.timestamps 22 | end 23 | 24 | create_table(:posts, :force => true) do |t| 25 | t.string :name 26 | t.string :title 27 | t.text :content 28 | t.integer :votes 29 | t.timestamps 30 | end 31 | 32 | end 33 | 34 | class Author < ActiveRecord::Base 35 | acts_as_xlsx 36 | has_many :comments 37 | end 38 | 39 | class Comment < ActiveRecord::Base 40 | acts_as_xlsx 41 | belongs_to :post 42 | belongs_to :author 43 | end 44 | 45 | class Post < ActiveRecord::Base 46 | acts_as_xlsx 47 | has_many :comments 48 | def ranking 49 | a = Post.find(:all, :order =>"votes desc") 50 | a.index(self) + 1 51 | end 52 | def last_comment 53 | self.comments.last.content 54 | end 55 | end 56 | 57 | posts = [] 58 | posts << Post.new(:name => "first post", :title => "This is the first post", :content=> "I am a very good first post!", :votes => 1) 59 | posts << Post.new(:name => "second post", :title => "This is the second post", :content=> "I am the best post!", :votes => 7) 60 | posts.each { |p| p.save! } 61 | 62 | authors = [] 63 | authors << Author.new(:name => 'bob') 64 | authors << Author.new(:name => 'joe') 65 | 66 | comments = [] 67 | comments << Comment.new(:post => posts[0], :content => "wow, that was a nice post!", :author=>authors[1]) 68 | comments << Comment.new(:content => "Are you really the best post?", :post => posts[1], :author=>authors[0]) 69 | comments << Comment.new(:content => "Only until someone posts better!", :post => posts[1], :author=>authors[0]) 70 | comments.each { |c| c.save } 71 | 72 | 73 | -------------------------------------------------------------------------------- /test/tc_acts_as_xlsx.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -w 2 | require 'test/unit' 3 | require "acts_as_xlsx.rb" 4 | require 'active_record' 5 | 6 | require File.expand_path(File.join(File.dirname(__FILE__), 'helper')) 7 | 8 | class TestActsAsXlsx < Test::Unit::TestCase 9 | 10 | class Post < ActiveRecord::Base 11 | acts_as_xlsx :columns=>[:name, :title, :content, :votes, :ranking], :i18n => 'activerecord.attributes' 12 | end 13 | 14 | def test_xlsx_options 15 | assert_equal([:name, :title, :content, :votes, :ranking], Post.xlsx_columns) 16 | assert_equal('activerecord.attributes', Post.xlsx_i18n) 17 | end 18 | 19 | end 20 | 21 | class TestToXlsx < Test::Unit::TestCase 22 | def test_to_xlsx_with_package 23 | p = Post.to_xlsx 24 | Post.to_xlsx :package=>p, :name=>'another posts' 25 | assert_equal p.workbook.worksheets.size, 2 26 | end 27 | 28 | def test_to_xlsx_with_name 29 | p = Post.to_xlsx :name=>'bob' 30 | assert_equal(p.workbook.worksheets.first.name, 'bob') 31 | end 32 | 33 | def test_xlsx_columns 34 | assert_equal( Post.xlsx_columns, Post.column_names.map {|c| c.to_sym}) 35 | end 36 | 37 | def test_to_xslx_vanilla 38 | p = Post.to_xlsx 39 | assert_equal("Id",p.workbook.worksheets.first.rows.first.cells.first.value) 40 | assert_equal(2,p.workbook.worksheets.first.rows.last.cells.first.value) 41 | end 42 | 43 | 44 | def test_to_xslx_with_provided_data 45 | p = Post.to_xlsx :data => Post.where(:title => "This is the first post").all 46 | assert_equal("Id",p.workbook.worksheets.first.rows.first.cells.first.value) 47 | assert_equal(1,p.workbook.worksheets.first.rows.last.cells.first.value) 48 | end 49 | 50 | 51 | def test_columns 52 | p = Post.to_xlsx :columns => [:name, :title, :content, :votes] 53 | sheet = p.workbook.worksheets.first 54 | assert_equal(sheet.rows.first.cells.size, Post.xlsx_columns.size - 3) 55 | assert_equal("Name",sheet.rows.first.cells.first.value) 56 | assert_equal(7,sheet.rows.last.cells.last.value) 57 | end 58 | 59 | def test_method_in_columns 60 | p = Post.to_xlsx :columns=>[:name, :votes, :content, :ranking] 61 | sheet = p.workbook.worksheets.first 62 | assert_equal("Name", sheet.rows.first.cells.first.value) 63 | assert_equal(Post.last.ranking, sheet.rows.last.cells.last.value) 64 | end 65 | 66 | def test_chained_method 67 | p = Post.to_xlsx :columns=>[:name, :votes, :content, :ranking, :'comments.last.content', :'comments.first.author.name'] 68 | sheet = p.workbook.worksheets.first 69 | assert_equal("Name", sheet.rows.first.cells.first.value) 70 | assert_equal(Post.last.comments.last.author.name, sheet.rows.last.cells.last.value) 71 | end 72 | 73 | 74 | 75 | end 76 | 77 | 78 | --------------------------------------------------------------------------------