Repository: comma-csv/comma Branch: master Commit: 0f639466331e Files: 48 Total size: 55.7 KB Directory structure: gitextract_uknkcqm5/ ├── .coveralls.yml ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .travis.yml ├── Appraisals ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── comma.gemspec ├── gemfiles/ │ ├── active6.0.6.gemfile │ ├── active6.1.7.6.gemfile │ ├── active7.0.8.gemfile │ ├── active7.1.3.gemfile │ ├── rails6.0.6.gemfile │ ├── rails6.1.7.6.gemfile │ ├── rails7.0.8.gemfile │ └── rails7.1.3.gemfile ├── init.rb ├── lib/ │ ├── comma/ │ │ ├── array.rb │ │ ├── data_extractor.rb │ │ ├── data_mapper_collection.rb │ │ ├── extractor.rb │ │ ├── generator.rb │ │ ├── header_extractor.rb │ │ ├── mongoid.rb │ │ ├── object.rb │ │ ├── relation.rb │ │ └── version.rb │ └── comma.rb └── spec/ ├── comma/ │ ├── comma_spec.rb │ ├── data_extractor_spec.rb │ ├── header_extractor_spec.rb │ └── rails/ │ ├── active_record_spec.rb │ ├── data_mapper_collection_spec.rb │ └── mongoid_spec.rb ├── controllers/ │ └── users_controller_spec.rb ├── non_rails_app/ │ └── ruby_classes.rb ├── rails_app/ │ ├── active_record/ │ │ ├── config.rb │ │ └── models.rb │ ├── data_mapper/ │ │ └── config.rb │ ├── mongoid/ │ │ └── config.rb │ ├── rails_app.rb │ └── tmp/ │ └── .gitkeep └── spec_helper.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .coveralls.yml ================================================ service_name: travis-ci ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: [push, pull_request] jobs: build: strategy: matrix: ruby: ['3.0', '3.1', '3.2', '3.3'] gemfile: ['active6.0.6', 'active6.1.7.6', 'active7.0.8', 'active7.1.3', 'rails6.0.6', 'rails6.1.7.6', 'rails7.0.8', 'rails7.1.3'] runs-on: ubuntu-latest env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile RUBY_OPT: --disable=did_you_mean services: mongodb: image: mongo:4.4.10 ports: - 8081:8081 steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true cache-version: 1 - run: bundle install --jobs 2 --retry 3 - run: bundle exec rubocop -P - run: bundle exec rspec -f d spec ================================================ FILE: .gitignore ================================================ pkg/* .*.swp *~ .bundle spec/rails_app/log spec/rails_app/db/development.sqlite* .rbx/ .ruby-* coverage/* ================================================ FILE: .rspec ================================================ --colour ================================================ FILE: .rubocop.yml ================================================ inherit_from: .rubocop_todo.yml require: rubocop-performance AllCops: DisplayCopNames: true Exclude: - 'gemfiles/**/*' - 'vendor/**/*' NewCops: enable TargetRubyVersion: 2.5 Layout/LineLength: IgnoreCopDirectives: true Max: 120 Naming/FileName: Exclude: - 'Appraisals' Naming/VariableNumber: EnforcedStyle: snake_case ================================================ FILE: .rubocop_todo.yml ================================================ # This configuration was generated by # `rubocop --auto-gen-config` # on 2024-01-20 09:02:51 UTC using RuboCop version 1.30.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Include. # Include: **/*.gemspec Gemspec/DeprecatedAttributeAssignment: Exclude: - 'comma.gemspec' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Include. # Include: **/*.gemspec Gemspec/RequireMFA: Exclude: - 'comma.gemspec' # Offense count: 1 # Configuration parameters: Include. # Include: **/*.gemspec Gemspec/RequiredRubyVersion: Exclude: - 'comma.gemspec' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, AllowAdjacentOneLineDefs, NumberOfEmptyLines. Layout/EmptyLineBetweenDefs: Exclude: - 'spec/rails_app/rails_app.rb' # Offense count: 4 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowAliasSyntax, AllowedMethods. # AllowedMethods: alias_method, public, protected, private Layout/EmptyLinesAroundAttributeAccessor: Exclude: - 'spec/comma/comma_spec.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Layout/SpaceAroundMethodCallOperator: Exclude: - 'spec/controllers/users_controller_spec.rb' # Offense count: 17 # Configuration parameters: AllowedMethods. # AllowedMethods: enums Lint/ConstantDefinitionInBlock: Exclude: - 'spec/comma/comma_spec.rb' - 'spec/comma/rails/active_record_spec.rb' - 'spec/comma/rails/data_mapper_collection_spec.rb' - 'spec/comma/rails/mongoid_spec.rb' # Offense count: 4 # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: - 'spec/comma/comma_spec.rb' - 'spec/comma/rails/data_mapper_collection_spec.rb' # Offense count: 1 Lint/MissingSuper: Exclude: - 'spec/comma/comma_spec.rb' # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). Lint/NonDeterministicRequireOrder: Exclude: - 'spec/spec_helper.rb' # Offense count: 2 # Configuration parameters: IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: Max: 22 # Offense count: 3 # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Max: 16 # Offense count: 2 # Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, camelCase Naming/VariableName: Exclude: - 'spec/comma/comma_spec.rb' # Offense count: 14 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object # FunctionalMethods: let, let!, subject, watch # IgnoredMethods: lambda, proc, it Style/BlockDelimiters: Exclude: - 'spec/comma/comma_spec.rb' - 'spec/comma/data_extractor_spec.rb' - 'spec/comma/header_extractor_spec.rb' # Offense count: 11 # Configuration parameters: AllowedConstants. Style/Documentation: Exclude: - 'spec/**/*' - 'test/**/*' - 'lib/comma/array.rb' - 'lib/comma/data_extractor.rb' - 'lib/comma/data_mapper_collection.rb' - 'lib/comma/extractor.rb' - 'lib/comma/generator.rb' - 'lib/comma/header_extractor.rb' - 'lib/comma/mongoid.rb' - 'lib/comma/object.rb' - 'lib/comma/relation.rb' # Offense count: 3 Style/MissingRespondToMissing: Exclude: - 'lib/comma/data_extractor.rb' - 'lib/comma/header_extractor.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Style/RedundantBegin: Exclude: - 'spec/spec_helper.rb' # Offense count: 6 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Mode. Style/StringConcatenation: Exclude: - 'spec/comma/comma_spec.rb' - 'spec/comma/rails/active_record_spec.rb' - 'spec/spec_helper.rb' ================================================ FILE: .travis.yml ================================================ sudo: false services: mongodb language: ruby cache: bundler rvm: - 2.4.10 - 2.5.8 - 2.6.6 - 2.7.2 gemfile: - gemfiles/active5.0.7.2.gemfile - gemfiles/active5.1.7.gemfile - gemfiles/active5.2.4.3.gemfile - gemfiles/active6.0.3.1.gemfile - gemfiles/active6.1.0.gemfile - gemfiles/rails5.0.7.2.gemfile - gemfiles/rails5.1.7.gemfile - gemfiles/rails5.2.4.3.gemfile - gemfiles/rails6.0.3.1.gemfile - gemfiles/rails6.1.0.gemfile - gemfiles/railsedge.gemfile matrix: exclude: - rvm: 2.4.10 gemfile: gemfiles/active6.0.3.1.gemfile - rvm: 2.4.10 gemfile: gemfiles/active6.1.0.gemfile - rvm: 2.4.10 gemfile: gemfiles/rails6.0.3.1.gemfile - rvm: 2.4.10 gemfile: gemfiles/rails6.1.0.gemfile - rvm: 2.4.10 gemfile: gemfiles/railsedge.gemfile - rvm: 2.5.8 gemfile: gemfiles/railsedge.gemfile fast_finish: true before_install: - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true - gem install bundler -v '< 2' script: - bundle exec rubocop -P - bundle exec rspec -f d spec ================================================ FILE: Appraisals ================================================ # frozen_string_literal: true appraise 'rails6.0.6' do gem 'rails', '6.0.6' gem 'rspec-rails' gem 'test-unit' end appraise 'active6.0.6' do gem 'activesupport', '6.0.6' gem 'activerecord', '6.0.6' end appraise 'rails6.1.7.6' do gem 'rails', '6.1.7.6' gem 'rspec-rails' gem 'test-unit' end appraise 'active6.1.7.6' do gem 'activesupport', '6.1.7.6' gem 'activerecord', '6.1.7.6' end appraise 'rails7.0.8' do gem 'rails', '7.0.8' gem 'rspec-rails' end appraise 'active7.0.8' do gem 'activesupport', '7.0.8' gem 'activerecord', '7.0.8' end appraise 'rails7.1.3' do gem 'rails', '7.1.3' gem 'rspec-rails' end appraise 'active7.1.3' do gem 'activesupport', '7.1.3' gem 'activerecord', '7.1.3' end ================================================ FILE: Gemfile ================================================ # frozen_string_literal: true source 'https://rubygems.org' gemspec gem 'coveralls', require: false gem 'rubocop', '~> 1.30.0', require: false gem 'rubocop-performance', require: false gem 'sqlite3' ================================================ FILE: MIT-LICENSE ================================================ Copyright (c) 2009 Marcus Crafter Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Comma A library to generate comma seperated value (CSV) for Ruby objects like ActiveRecord and Array [![Gem Version](https://badge.fury.io/rb/comma.svg)](http://badge.fury.io/rb/comma) [![Build Status](https://github.com/comma-csv/comma/actions/workflows/build.yml/badge.svg)](https://github.com/comma-csv/comma/actions/workflows/build.yml) [![Code Climate](https://codeclimate.com/github/comma-csv/comma.svg)](https://codeclimate.com/github/comma-csv/comma) ## Getting Started ### Prerequisites You need to use ruby 3.0 or later. If you generate CSV from ActiveRecord models, you need to have ActiveRecord 6.0 or later. ### Installing Comma is distributed as a gem, best installed via Bundler. Include the gem in your Gemfile: ```ruby gem 'comma', '~> 4.8.0' ``` Or, if you want to live life on the edge, you can get master from the main comma repository: ```ruby gem 'comma', git: 'git://github.com/comma-csv/comma.git' ``` Then, run `bundle install`. ### Usage See [this page](https://github.com/comma-csv/comma/wiki) for usages. ## Running the tests To run the test suite across multiple gem file sets, we're using [Appraisal](https://github.com/thoughtbot/appraisal), use the following commands: ```sh $ bundle exec appraisal install $ bundle exec appraisal rake spec ``` ## Contributing ## Versioning We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/comma-csv/comma/tags). ## Authors * Marcus Crafter - Initial work * Tom Meier - Initial work * Eito Katagiri ## License This project is licensed under the MIT License - see the [MIT-LICENSE](https://github.com/comma-csv/comma/blob/master/MIT-LICENSE) file fore details. ================================================ FILE: Rakefile ================================================ # frozen_string_literal: true require 'bundler/setup' require 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = FileList['spec/**/*_spec.rb'] end require 'rubocop/rake_task' RuboCop::RakeTask.new task default: :spec ================================================ FILE: comma.gemspec ================================================ # frozen_string_literal: true $LOAD_PATH.push File.expand_path('lib', __dir__) require 'comma/version' Gem::Specification.new do |s| s.name = 'comma' s.version = Comma::VERSION s.authors = ['Marcus Crafter', 'Tom Meier'] s.email = ['crafterm@redartisan.com', 'tom@venombytes.com'] s.homepage = 'http://github.com/comma-csv/comma' s.summary = %(Ruby Comma Seperated Values generation library) s.description = %(Ruby Comma Seperated Values generation library) s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.require_paths = ['lib'] s.licenses = ['MIT'] s.add_dependency 'activesupport', '>= 4.2.0' s.add_development_dependency 'appraisal', ['~> 1.0.0'] s.add_development_dependency 'minitest', '5.14.4' s.add_development_dependency 'rake', '~> 13.0.1' s.add_development_dependency 'rspec', ['~> 3.5.0'] s.add_development_dependency 'rspec-activemodel-mocks' s.add_development_dependency 'rspec-its' end ================================================ FILE: gemfiles/active6.0.6.gemfile ================================================ # This file was generated by Appraisal source "https://rubygems.org" gem "coveralls", :require => false gem "rubocop", "~> 1.30.0", :require => false gem "rubocop-performance", :require => false gem "sqlite3" gem "activesupport", "6.0.6" gem "activerecord", "6.0.6" gemspec :path => "../" ================================================ FILE: gemfiles/active6.1.7.6.gemfile ================================================ # This file was generated by Appraisal source "https://rubygems.org" gem "coveralls", :require => false gem "rubocop", "~> 1.30.0", :require => false gem "rubocop-performance", :require => false gem "sqlite3" gem "activesupport", "6.1.7.6" gem "activerecord", "6.1.7.6" gemspec :path => "../" ================================================ FILE: gemfiles/active7.0.8.gemfile ================================================ # This file was generated by Appraisal source "https://rubygems.org" gem "coveralls", :require => false gem "rubocop", "~> 1.30.0", :require => false gem "rubocop-performance", :require => false gem "sqlite3" gem "activesupport", "7.0.8" gem "activerecord", "7.0.8" gemspec :path => "../" ================================================ FILE: gemfiles/active7.1.3.gemfile ================================================ # This file was generated by Appraisal source "https://rubygems.org" gem "coveralls", :require => false gem "rubocop", "~> 1.30.0", :require => false gem "rubocop-performance", :require => false gem "sqlite3" gem "activesupport", "7.1.3" gem "activerecord", "7.1.3" gemspec :path => "../" ================================================ FILE: gemfiles/rails6.0.6.gemfile ================================================ # This file was generated by Appraisal source "https://rubygems.org" gem "coveralls", :require => false gem "rubocop", "~> 1.30.0", :require => false gem "rubocop-performance", :require => false gem "sqlite3" gem "rails", "6.0.6" gem "rspec-rails" gem "test-unit" gemspec :path => "../" ================================================ FILE: gemfiles/rails6.1.7.6.gemfile ================================================ # This file was generated by Appraisal source "https://rubygems.org" gem "coveralls", :require => false gem "rubocop", "~> 1.30.0", :require => false gem "rubocop-performance", :require => false gem "sqlite3" gem "rails", "6.1.7.6" gem "rspec-rails" gem "test-unit" gemspec :path => "../" ================================================ FILE: gemfiles/rails7.0.8.gemfile ================================================ # This file was generated by Appraisal source "https://rubygems.org" gem "coveralls", :require => false gem "rubocop", "~> 1.30.0", :require => false gem "rubocop-performance", :require => false gem "sqlite3" gem "rails", "7.0.8" gem "rspec-rails" gemspec :path => "../" ================================================ FILE: gemfiles/rails7.1.3.gemfile ================================================ # This file was generated by Appraisal source "https://rubygems.org" gem "coveralls", :require => false gem "rubocop", "~> 1.30.0", :require => false gem "rubocop-performance", :require => false gem "sqlite3" gem "rails", "7.1.3" gem "rspec-rails" gemspec :path => "../" ================================================ FILE: init.rb ================================================ # frozen_string_literal: true require 'comma' ================================================ FILE: lib/comma/array.rb ================================================ # frozen_string_literal: true class Array def to_comma(style = :default) Comma::Generator.new(self, style).run(:each) end end ================================================ FILE: lib/comma/data_extractor.rb ================================================ # frozen_string_literal: true require 'comma/extractor' module Comma class DataExtractor < Extractor class ExtractValueFromInstance def initialize(instance) @instance = instance end def extract(sym, &block) yield_block_with_value(extract_value(sym), &block) end private def yield_block_with_value(value, &block) block ? yield(value) : value end def extract_value(method) extraction_object.send(method) end def extraction_object @instance end end class ExtractValueFromAssociationOfInstance < ExtractValueFromInstance def initialize(instance, association_name) super(instance) @association_name = association_name end private def extraction_object @instance.send(@association_name) || null_association end def null_association @null_association ||= Class.new(Class.const_defined?(:BasicObject) ? ::BasicObject : ::Object) do def method_missing(_symbol, *_args, &_block) nil end end.new end end def method_missing(sym, *args, &block) @results << ExtractValueFromInstance.new(@instance).extract(sym, &block) if args.blank? args.each do |arg| case arg when Hash arg.each do |k, _v| @results << ExtractValueFromAssociationOfInstance.new(@instance, sym).extract(k, &block) end when Symbol @results << ExtractValueFromAssociationOfInstance.new(@instance, sym).extract(arg, &block) when String @results << ExtractValueFromInstance.new(@instance).extract(sym, &block) else raise "Unknown data symbol #{arg.inspect}" end end end def __static_column__(_header = nil, &block) @results << (block ? yield(@instance) : nil) end end end ================================================ FILE: lib/comma/data_mapper_collection.rb ================================================ # frozen_string_literal: true if defined?(DataMapper) module DataMapper class Collection def to_comma(style = :default) Comma::Generator.new(self, style).run(:each) end end end end ================================================ FILE: lib/comma/extractor.rb ================================================ # frozen_string_literal: true module Comma class Extractor def initialize(instance, style, formats) @instance = instance @style = style @formats = formats @results = [] end def results instance_eval(&@formats[@style]) @results.map { |r| convert_to_data_value(r) } end def id(*args, &block) method_missing(:id, *args, &block) end def __use__(style) # TODO: prevent infinite recursion instance_eval(&@formats[style]) end private def convert_to_data_value(result) result.nil? ? result : result.to_s end end end ================================================ FILE: lib/comma/generator.rb ================================================ # frozen_string_literal: true module Comma class Generator def initialize(instance, style) @instance = instance @style = style @options = {} return unless @style.is_a?(Hash) @options = @style.clone @style = @options.delete(:style) || Comma::DEFAULT_OPTIONS[:style] @filename = @options.delete(:filename) end def run(iterator_method) if @filename CSV_HANDLER.open(@filename, 'w', **@options) { |csv| append_csv(csv, iterator_method) } && (return true) else CSV_HANDLER.generate(**@options) { |csv| append_csv(csv, iterator_method) } end end private def append_csv(csv, iterator_method) return '' if @instance.empty? csv << @instance.first.to_comma_headers(@style) unless @options.key?(:write_headers) && !@options[:write_headers] @instance.send(iterator_method) do |object| csv << object.to_comma(@style) end end end end ================================================ FILE: lib/comma/header_extractor.rb ================================================ # frozen_string_literal: true require 'comma/extractor' require 'active_support' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/inflections' module Comma class HeaderExtractor < Extractor class_attribute :value_humanizer DEFAULT_VALUE_HUMANIZER = lambda do |value, _model_class| value.is_a?(String) ? value : value.to_s.humanize end self.value_humanizer = DEFAULT_VALUE_HUMANIZER def method_missing(sym, *args, &_block) model_class = @instance.class @results << value_humanizer.call(sym, model_class) if args.blank? args.each do |arg| case arg when Hash arg.each do |_k, v| @results << value_humanizer.call(v, get_association_class(model_class, sym)) end when Symbol @results << value_humanizer.call(arg, get_association_class(model_class, sym)) when String @results << value_humanizer.call(arg, model_class) else raise "Unknown header symbol #{arg.inspect}" end end end def __static_column__(header = '', &_block) @results << header end private def get_association_class(model_class, association) return unless model_class.respond_to?(:reflect_on_association) begin model_class.reflect_on_association(association)&.klass rescue ArgumentError, NameError # Since Rails 5.2, ArgumentError is raised. nil end end end end ================================================ FILE: lib/comma/mongoid.rb ================================================ # frozen_string_literal: true # Conditionally set to_comma on Mongoid records if mongoid gem is installed begin require 'mongoid' module Mongoid class Criteria def to_comma(style = :default) Comma::Generator.new(self, style).run(:each) end end end rescue LoadError => e warn e.inspect end ================================================ FILE: lib/comma/object.rb ================================================ # frozen_string_literal: true require 'comma/data_extractor' require 'comma/header_extractor' class Object class_attribute :comma_formats class << self def comma(style = :default, &block) (self.comma_formats ||= {})[style] = block end def inherited(subclass) super subclass.comma_formats = self.comma_formats ? self.comma_formats.dup : {} end end def to_comma(style = :default) extract_with(Comma::DataExtractor, style) end def to_comma_headers(style = :default) extract_with(Comma::HeaderExtractor, style) end private def extract_with(extractor_class, style = :default) raise_unless_style_exists(style) extractor_class.new(self, style, self.comma_formats).results end def raise_unless_style_exists(style) return if self.comma_formats && self.comma_formats[style] raise "No comma format for class #{self.class} defined for style #{style}" end end ================================================ FILE: lib/comma/relation.rb ================================================ # frozen_string_literal: true module ActiveRecord class Relation def to_comma(style = :default) iterator_method = if arel.ast.limit || !arel.ast.orders.empty? Rails.logger.warn { <<~WARN } if defined?(Rails) #to_comma is being used on a relation with limit or order clauses. Falling back to iterating with :each. This can cause performance issues. WARN :each else :find_each end Comma::Generator.new(self, style).run(iterator_method) end end end ================================================ FILE: lib/comma/version.rb ================================================ # frozen_string_literal: true module Comma VERSION = '4.8.0' end ================================================ FILE: lib/comma.rb ================================================ # frozen_string_literal: true require 'csv' CSV_HANDLER = CSV module Comma DEFAULT_OPTIONS = { write_headers: true, style: :default }.freeze end require 'active_support' require 'active_support/lazy_load_hooks' ActiveSupport.on_load(:active_record) do require 'comma/relation' if defined?(ActiveRecord::Relation) end ActiveSupport.on_load(:mongoid) do require 'comma/mongoid' end require 'comma/data_mapper_collection' if defined? DataMapper require 'comma/generator' require 'comma/array' require 'comma/object' # Load into Rails controllers ActiveSupport.on_load(:action_controller) do if defined?(ActionController::Renderers) && ActionController::Renderers.respond_to?(:add) ActionController::Renderers.add :csv do |obj, options| filename = options[:filename] || 'data' extension = options[:extension] || 'csv' mime_type = if Rails.version >= '5.0.0' options[:mime_type] || Mime[:csv] else options[:mime_type] || Mime::CSV end with_bom = options.delete(:with_bom) || false # Capture any CSV optional settings passed to comma or comma specific options csv_options = options.slice(*CSV_HANDLER::DEFAULT_OPTIONS.merge(Comma::DEFAULT_OPTIONS).keys) csv_options = csv_options.each_with_object({}) do |(k, v), h| # XXX: Convert string to boolean h[k] = case k when :write_headers (v != 'false') if v.is_a?(String) else v end end data = obj.to_comma(csv_options) data = "\xEF\xBB\xBF#{data}" if with_bom disposition = "attachment; filename=\"#{filename}.#{extension}\"" send_data data, type: mime_type, disposition: disposition end end end ================================================ FILE: spec/comma/comma_spec.rb ================================================ # frozen_string_literal: true require File.dirname(__FILE__) + '/../spec_helper' describe Comma do it 'should extend object to add a comma method' do expect(Object).to respond_to(:comma) end it 'should extend object to have a to_comma method' do expect(Object).to respond_to(:to_comma) end it 'should extend object to have a to_comma_headers method' do expect(Object).to respond_to(:to_comma_headers) end describe '.to_comma_header' do it 'should not crash (#94)' do klass = Class.new klass.instance_eval do attr_accessor :name comma :brief do name end end expect { klass.to_comma_headers(:brief) }.to_not raise_error end end end describe Comma, 'generating CSV' do # rubocop:disable Metrics/BlockLength before do @isbn = Isbn.new('123123123', '321321321') @book = Book.new('Smalltalk-80', 'Language and Implementation', @isbn) @books = [] @books << @book end it 'should extend Array to add a #to_comma method which will return CSV content for objects within the array' do expected = "Title,Description,Issuer,ISBN-10,ISBN-13\nSmalltalk-80,Language and Implementation,ISBN,123123123,321321321\n" # rubocop:disable Layout/LineLength expect(@books.to_comma).to eq(expected) end it 'should return an empty string when generating CSV from an empty array' do expect([].to_comma).to eq('') end it 'should change the style when specified' do expect(@books.to_comma(:brief)).to eq("Name,Description\nSmalltalk-80,Language and Implementation\n") end describe 'with :filename specified' do after { File.delete('comma.csv') } it 'should write to the file' do @books.to_comma(filename: 'comma.csv') expected = "Title,Description,Issuer,ISBN-10,ISBN-13\nSmalltalk-80,Language and Implementation,ISBN,123123123,321321321\n" # rubocop:disable Layout/LineLength expect(File.read('comma.csv')).to eq(expected) end it 'should accept FasterCSV options' do @books.to_comma(filename: 'comma.csv', col_sep: ';', force_quotes: true) expected = "\"Title\";\"Description\";\"Issuer\";\"ISBN-10\";\"ISBN-13\"\n\"Smalltalk-80\";\"Language and Implementation\";\"ISBN\";\"123123123\";\"321321321\"\n" # rubocop:disable Layout/LineLength expect(File.read('comma.csv')).to eq(expected) end end describe 'with FasterCSV options' do it 'should not change when options are empty' do expected = "Title,Description,Issuer,ISBN-10,ISBN-13\nSmalltalk-80,Language and Implementation,ISBN,123123123,321321321\n" # rubocop:disable Layout/LineLength expect(@books.to_comma({})).to eq(expected) end it 'should accept the options in #to_comma and generate the appropriate CSV' do expected = "\"Title\";\"Description\";\"Issuer\";\"ISBN-10\";\"ISBN-13\"\n\"Smalltalk-80\";\"Language and Implementation\";\"ISBN\";\"123123123\";\"321321321\"\n" # rubocop:disable Layout/LineLength expect(@books.to_comma(col_sep: ';', force_quotes: true)).to eq(expected) end it 'should change the style when specified' do expect(@books.to_comma(style: :brief, col_sep: ';', force_quotes: true)) .to eq("\"Name\";\"Description\"\n\"Smalltalk-80\";\"Language and Implementation\"\n") end end end describe Comma, 'defining CSV descriptions' do describe 'with an unnamed description' do before do class Foo comma do; end end end it 'should name the current description :default if no name has been provided' do expect(Foo.comma_formats).not_to be_empty expect(Foo.comma_formats[:default]).not_to be_nil end end describe 'with a named description' do before do class Bar comma do; end comma :detailed do; end end end it 'should use the provided name to index the comma format' do expect(Bar.comma_formats).not_to be_empty expect(Bar.comma_formats[:default]).not_to be_nil expect(Bar.comma_formats[:detailed]).not_to be_nil end end end describe Comma, 'to_comma data/headers object extensions' do # rubocop:disable Metrics/BlockLength describe 'with unnamed descriptions' do before do class Foo attr_accessor :content comma do; content; end def initialize(content) @content = content end end @foo = Foo.new('content') end it 'should return and array of data content, using the :default CSV description if none requested' do expect(@foo.to_comma).to eq(%w[content]) end it 'should return and array of header content, using the :default CSV description if none requested' do expect(@foo.to_comma_headers).to eq(%w[Content]) end it 'should return the CSV representation including header and content when called on an array' do expect(Array(@foo).to_comma).to eq("Content\ncontent\n") end end describe 'with named descriptions' do before do class Foo attr_accessor :content comma :detailed do; content; end def initialize(content) @content = content end end @foo = Foo.new('content') end it 'should return and array of data content, using the :default CSV description if none requested' do expect(@foo.to_comma(:detailed)).to eq(%w[content]) end it 'should return and array of header content, using the :default CSV description if none requested' do expect(@foo.to_comma_headers(:detailed)).to eq(%w[Content]) end it 'should return the CSV representation including header and content when called on an array' do expect(Array(@foo).to_comma(:detailed)).to eq("Content\ncontent\n") end it 'should raise an error if the requested description is not avaliable' do expect { @foo.to_comma(:bad) }.to raise_error(RuntimeError) expect { @foo.to_comma_headers(:bad) }.to raise_error(RuntimeError) expect { Array(@foo).to_comma(:bad) }.to raise_error(RuntimeError) end end describe 'with block' do # rubocop:disable Metrics/BlockLength before do class Foo attr_accessor :content, :created_at, :updated_at comma do content content('Truncated Content') { |i| i && i.length > 10 ? i[0..10] : '---' } created_at { |i| i&.to_formatted_s(:db) } updated_at { |i| i&.to_formatted_s(:db) } created_at 'Created Custom Label' do |i| i&.to_formatted_s(:short) end updated_at 'Updated at Custom Label' do |i| i&.to_formatted_s(:short) end end def initialize(content, created_at = Time.now, updated_at = Time.now) @content = content @created_at = created_at @updated_at = updated_at end end @time = Time.now @content = 'content ' * 5 @foo = Foo.new @content, @time, @time end it 'should return yielded values by block' do _header, foo = Array(@foo).to_comma.split("\n") expected = [ @content, @content[0..10], @time.to_formatted_s(:db), @time.to_formatted_s(:db), @time.to_formatted_s(:short), @time.to_formatted_s(:short) ].join(',') expect(foo).to eq(expected) end it 'should return headers with custom labels from block' do header, _foo = Array(@foo).to_comma.split("\n") expected = [ 'Content', 'Truncated Content', 'Created at', 'Updated at', 'Created Custom Label', 'Updated at Custom Label' ].join(',') expect(header).to eq(expected) end it 'should put headers in place when forced' do header, _foo = Array(@foo).to_comma(write_headers: true).split("\n") expected = [ 'Content', 'Truncated Content', 'Created at', 'Updated at', 'Created Custom Label', 'Updated at Custom Label' ].join(',') expect(header).to eq(expected) end it 'should not write headers if specified' do header, _foo = Array(@foo).to_comma(write_headers: false).split("\n") expected = [ @content, @content[0..10], @time.to_formatted_s(:db), @time.to_formatted_s(:db), @time.to_formatted_s(:short), @time.to_formatted_s(:short) ].join(',') expect(header).to eq(expected) end end describe 'on an object with no comma declaration' do it 'should raise an error mentioning there is no comma description defined for that class' do expect { 'a string'.to_comma }.to raise_error('No comma format for class String defined for style default') expect { 'a string'.to_comma_headers } .to raise_error('No comma format for class String defined for style default') end end describe 'on objects using Single Table Inheritance' do before do class MySuperClass attr_accessor :content comma do; content end def initialize(content) @content = 'super-' + content end end class ChildClassComma < MySuperClass comma do; content end def initialize(content) @content = 'sub-' + content end end class ChildClassNoComma < MySuperClass end @childComma = ChildClassComma.new('content') @childNoComma = ChildClassNoComma.new('content') end it 'should return and array of data content, as defined in comma block in child class' do expect(@childComma.to_comma).to eq(%w[sub-content]) end it 'should return and array of data content, as defined in comma block in super class, if not present in child' do expect(@childNoComma.to_comma).to eq(%w[super-content]) end end end describe Comma, '__use__ keyword' do before(:all) do @obj = Class.new(Struct.new(:id, :title, :description)) do comma do title __use__ :description end comma :description do __use__ :static description end comma :static do __static_column__ do 'Foo, Inc.' end end end.new(1, 'Programming Ruby', 'The Pickaxe book') end subject { @obj.to_comma } its(:size) { should eq(3) } it { should eq(['Programming Ruby', 'Foo, Inc.', 'The Pickaxe book']) } end ================================================ FILE: spec/comma/data_extractor_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' # comma do # name 'Title' # description # # isbn :number_10 => 'ISBN-10', :number_13 => 'ISBN-13' # end describe Comma::DataExtractor do # rubocop:disable Metrics/BlockLength before do @isbn = Isbn.new('123123123', '321321321') @book = Book.new('Smalltalk-80', 'Language and Implementation', @isbn) @data = @book.to_comma end describe 'when no parameters are provided' do it 'should use the string value returned by sending the method name on the object' do expect(@data).to include('Language and Implementation') end end describe 'when given a string description as a parameter' do it 'should use the string value returned by sending the method name on the object' do expect(@data).to include('Smalltalk-80') end end describe 'when an hash is passed as a parameter' do describe 'with a string value' do it 'should use the string value, returned by sending the hash key to the object' do expect(@data).to include('123123123') expect(@data).to include('321321321') end it 'should not fail when an associated object is nil' do expect { Book.new('Smalltalk-80', 'Language and Implementation', nil).to_comma }.not_to raise_error end end end end describe Comma::DataExtractor, 'id attribute' do before do @data = Class.new(Struct.new(:id)) do comma do id 'ID' do |_id| '42' end end end.new(1).to_comma end it 'id attribute should yield block' do expect(@data).to include('42') end end describe Comma::DataExtractor, 'with static column method' do before do @data = Class.new(Struct.new(:id, :name)) do comma do __static_column__ __static_column__ 'STATIC' __static_column__ 'STATIC' do '' end __static_column__ 'STATIC', &:name end end.new(1, 'John Doe').to_comma end it 'should extract headers' do expect(@data).to eq([nil, nil, '', 'John Doe']) end end describe Comma::DataExtractor, 'nil value' do before do @data = Class.new(Struct.new(:id, :name)) do comma do name name 'Name' name 'Name' do |_name| nil end end end.new(1, nil).to_comma end it 'should extract nil' do expect(@data).to eq([nil, nil, nil]) end end ================================================ FILE: spec/comma/header_extractor_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' # comma do # name 'Title' # description # # isbn :number_10 => 'ISBN-10', :number_13 => 'ISBN-13' # end describe Comma::HeaderExtractor do # rubocop:disable Metrics/BlockLength before do @isbn = Isbn.new('123123123', '321321321') @book = Book.new('Smalltalk-80', 'Language and Implementation', @isbn) @headers = @book.to_comma_headers end describe 'when no parameters are provided' do it 'should use the method name as the header name, humanized' do expect(@headers).to include('Description') end end describe 'when given a string description as a parameter' do it 'should use the string value, unmodified' do expect(@headers).to include('Title') end end describe 'when an hash is passed as a parameter' do describe 'with a string value' do it 'should use the string value, unmodified' do expect(@headers).to include('ISBN-10') end end describe 'with a non-string value' do it 'should use the non string value converted to a string, humanized' do expect(@headers).to include('Issuer') end end end end describe Comma::HeaderExtractor, 'with static column method' do before do @headers = Class.new(Struct.new(:id, :name)) do comma do __static_column__ __static_column__ 'STATIC' __static_column__ 'STATIC' do '' end end end.new(1, 'John Doe').to_comma_headers end it 'should extract headers' do expect(@headers).to eq(['', 'STATIC', 'STATIC']) end end ================================================ FILE: spec/comma/rails/active_record_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' if defined? ActiveRecord describe Comma, 'generating CSV from an ActiveRecord object' do # rubocop:disable Metrics/BlockLength class Picture < ActiveRecord::Base belongs_to :imageable, polymorphic: true comma :pr_83 do imageable name: 'Picture' end end class Person < ActiveRecord::Base scope(:teenagers, -> { where(age: 13..19) }) comma do name age end has_one :job comma :issue_75 do job :title end has_many :pictures, as: :imageable end class Job < ActiveRecord::Base belongs_to :person comma do person_formatter name: 'Name' end def person_formatter @person_formatter ||= PersonFormatter.new(person) end end class PersonFormatter def initialize(persor) @person = persor end def name @person.name end end before(:all) do # Setup AR model in memory ActiveRecord::Base.connection.create_table :pictures, force: true do |table| table.column :name, :string table.column :imageable_id, :integer table.column :imageable_type, :string end ActiveRecord::Base.connection.create_table :people, force: true do |table| table.column :name, :string table.column :age, :integer end Person.reset_column_information ActiveRecord::Base.connection.create_table :jobs, force: true do |table| table.column :person_id, :integer table.column :title, :string end Job.reset_column_information @person = Person.new(age: 18, name: 'Junior') @person.build_job(title: 'Nice job') @person.save! Picture.create(name: 'photo.jpg', imageable_id: @person.id, imageable_type: 'Person') end describe '#to_comma on scopes' do it 'should extend ActiveRecord::NamedScope::Scope to add a #to_comma method which will return CSV content for objects within the scope' do # rubocop:disable Layout/LineLength expect(Person.teenagers.to_comma).to eq "Name,Age\nJunior,18\n" end it 'should find in batches' do scope = Person.teenagers expect(scope).to receive(:find_each).and_yield @person scope.to_comma end it 'should fall back to iterating with each when scope has limit clause' do scope = Person.limit(1) expect(scope).to receive(:each).and_yield @person scope.to_comma end it 'should fall back to iterating with each when scope has order clause' do scope = Person.order(:age) expect(scope).to receive(:each).and_yield @person scope.to_comma end end describe 'with custom value_humanizer' do before do Comma::HeaderExtractor.value_humanizer = lambda do |value, model_class| if model_class.respond_to?(:human_attribute_name) model_class.human_attribute_name(value) else value.is_a?(String) ? value : value.to_s.humanize end end I18n.config.backend.store_translations(:ja, activerecord: { attributes: { person: { age: '年齢', name: '名前' } } }) @original_locale = I18n.locale I18n.locale = :ja end after do I18n.locale = @original_locale Comma::HeaderExtractor.value_humanizer = Comma::HeaderExtractor::DEFAULT_VALUE_HUMANIZER end it 'should i18n-ize header values' do expect(Person.teenagers.to_comma).to match(/^名前,年齢/) end end describe 'github issue 75' do it 'should find association' do expect { Person.all.to_comma(:issue_75) }.not_to raise_error end end describe 'with accessor' do it 'should not raise exception' do expect(Job.all.to_comma).to eq("Name\nJunior\n") end end describe 'github pull-request 83' do it 'should not raise NameError' do expect { Picture.all.to_comma(:pr_83) }.not_to raise_error end end end describe Comma, 'generating CSV from an ActiveRecord object using Single Table Inheritance' do # rubocop:disable Metrics/BlockLength class Animal < ActiveRecord::Base comma do name 'Name' do |name| 'Super-' + name end end comma :with_type do name type end end class Dog < Animal comma do name 'Name' do |name| 'Dog-' + name end end end class Cat < Animal end before(:all) do # Setup AR model in memory ActiveRecord::Base.connection.create_table :animals, force: true do |table| table.column :name, :string table.column :type, :string end @dog = Dog.new(name: 'Rex') @dog.save! @cat = Cat.new(name: 'Kitty') @cat.save! end it 'should return and array of data content, as defined in comma block in child class' do expect(@dog.to_comma).to eq %w[Dog-Rex] end # FIXME: this one is failing - the comma block from Dog is executed instead of the one from the super class it 'should return and array of data content, as defined in comma block in super class, if not present in child' do expect(@cat.to_comma).to eq %w[Super-Kitty] end it 'should call definion in parent class' do expect { @dog.to_comma(:with_type) }.not_to raise_error end end end ================================================ FILE: spec/comma/rails/data_mapper_collection_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' if defined? DataMapper describe Comma, 'generating CSV from an DataMapper object' do # rubocop:disable Metrics/BlockLength class Person include DataMapper::Resource property :id, Serial property :name, String property :age, Integer def self.teenagers all(:age.gte => 13) & all(:age.lte => 19) end comma do name age end end DataMapper.finalize before(:all) do DataMapper.setup(:default, 'sqlite::memory:') DataMapper.auto_migrate! end after(:all) do end describe 'case' do before do @person = Person.new(age: 18, name: 'Junior') @person.save end it 'should extend scope to add a #to_comma method which will return CSV content for objects within the scope' do Person.teenagers.to_comma.should == "Name,Age\nJunior,18\n" end it 'should find in batches' do Person.teenagers.to_comma end end end end ================================================ FILE: spec/comma/rails/mongoid_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' if defined? Mongoid describe Comma, 'generating CSV from an Mongoid object' do class Person include Mongoid::Document field :name, type: String field :age, type: Integer scope :teenagers, between(age: 13..19) comma do name age end end after(:all) do Mongoid.purge! end describe 'case' do before do @person = Person.new(age: 18, name: 'Junior') @person.save end it 'should extend ActiveRecord::NamedScope::Scope to add a #to_comma method which will return CSV content for objects within the scope' do # rubocop:disable Layout/LineLength Person.teenagers.to_comma.should == "Name,Age\nJunior,18\n" end it 'should find in batches' do Person.teenagers.to_comma end end end end ================================================ FILE: spec/controllers/users_controller_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' if defined?(Rails) RSpec.describe UsersController, type: :controller do # rubocop:disable Metrics/BlockLength describe 'rails setup' do it 'should capture the CSV renderer provided by Rails' do mock_users = [mock_model(User), mock_model(User)] allow(User).to receive(:all).and_return(mock_users) expect(mock_users).to receive(:to_comma).once get :index, format: :csv end end describe 'controller' do # rubocop:disable Metrics/BlockLength before(:all) do @user_1 = User.create!(first_name: 'Fred', last_name: 'Flintstone') @user_2 = User.create!(first_name: 'Wilma', last_name: 'Flintstone') end it 'should not affect html requested' do get :index expect(response.status).to eq 200 expect(response.media_type).to eq 'text/html' expect(response.body).to eq 'Users!' end it 'should return a csv when requested' do get :index, format: :csv expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' expect(response.header['Content-Disposition']).to include('filename="data.csv"') expected_content = <<-CSV.gsub(/^\s+/, '') First name,Last name,Name Fred,Flintstone,Fred Flintstone Wilma,Flintstone,Wilma Flintstone CSV expect(response.body).to eq expected_content end describe 'with comma options' do it 'should allow the style to be chosen from the renderer' do # Must be passed in same format (string/symbol) eg: # format.csv { render User.all, :style => :shortened } get :with_custom_style, format: :csv expected_content = <<-CSV.gsub(/^\s+/, '') First name,Last name Fred,Flintstone Wilma,Flintstone CSV expect(response.body).to eq expected_content end end describe 'with custom options' do # rubocop:disable Metrics/BlockLength def is_rails_4? Rails::VERSION::STRING =~ /^4.*/ end def get_(name, **args) if is_rails_4? && args[:params] args.merge!(args[:params]) args.delete(:params) end get name, **args end it 'should allow a filename to be set' do get_ :with_custom_options, format: :csv, params: { custom_options: { filename: 'my_custom_name' } } expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' expect(response.header['Content-Disposition']).to include('filename="my_custom_name.csv"') end it 'should allow a custom filename with spaces' do require 'shellwords' params = { custom_options: { filename: 'filename with a lot of spaces' } } get_ :with_custom_options, format: :csv, params: params expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' expect(response.header['Content-Disposition']).to include('filename="filename with a lot of spaces.csv"') filename_string = response.header['Content-Disposition'].split('=').last # shellsplit honors quoted strings expect(filename_string.shellsplit.length).to eq 1 end it 'should allow a file extension to be set' do get_ :with_custom_options, format: :csv, params: { custom_options: { extension: :txt } } expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' expect(response.header['Content-Disposition']).to include('filename="data.txt"') end it 'should allow mime type to be set' do get_ :with_custom_options, format: :csv, params: { custom_options: { mime_type: 'text/plain' } } expect(response.status).to eq 200 expect(response.media_type).to eq 'text/plain' end it 'should allow bom to be set' do get_ :with_custom_options, format: :csv, params: { custom_options: { with_bom: true } } expected_content = <<-CSV.gsub(/^\s+/, '') \xEF\xBB\xBFFirst name,Last name,Name Fred,Flintstone,Fred Flintstone Wilma,Flintstone,Wilma Flintstone CSV expect(response.body). to eq expected_content end describe 'headers' do it 'should allow toggling on' do get_ :with_custom_options, format: :csv, params: { custom_options: { write_headers: 'true' } } expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' expected_content = <<-CSV.gsub(/^\s+/, '') First name,Last name,Name Fred,Flintstone,Fred Flintstone Wilma,Flintstone,Wilma Flintstone CSV expect(response.body).to eq expected_content end it 'should allow toggling off' do get_ :with_custom_options, format: :csv, params: { custom_options: { write_headers: false } } expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' expected_content = <<-CSV.gsub(/^\s+/, '') Fred,Flintstone,Fred Flintstone Wilma,Flintstone,Wilma Flintstone CSV expect(response.body).to eq expected_content end end it 'should allow forcing of quotes' do get_ :with_custom_options, format: :csv, params: { custom_options: { force_quotes: true } } expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' expected_content = <<-CSV.gsub(/^\s+/, '') "First name","Last name","Name" "Fred","Flintstone","Fred Flintstone" "Wilma","Flintstone","Wilma Flintstone" CSV expect(response.body).to eq expected_content end it 'should allow combinations of options' do params = { custom_options: { write_headers: false, force_quotes: true, col_sep: '||', row_sep: "ENDOFLINE\n" } } get_ :with_custom_options, format: :csv, params: params expect(response.status).to eq 200 expect(response.media_type).to eq 'text/csv' expected_content = <<-CSV.gsub(/^\s+/, '') "Fred"||"Flintstone"||"Fred Flintstone"ENDOFLINE "Wilma"||"Flintstone"||"Wilma Flintstone"ENDOFLINE CSV expect(response.body).to eq expected_content end end end end end ================================================ FILE: spec/non_rails_app/ruby_classes.rb ================================================ # frozen_string_literal: true class Book attr_accessor :name, :description, :isbn def initialize(name, description, isbn) @name = name @description = description @isbn = isbn end comma do name 'Title' description isbn authority: :issuer isbn number_10: 'ISBN-10' isbn number_13: 'ISBN-13' end comma :brief do name description end end class Isbn attr_accessor :number_10, :number_13 def initialize(isbn_10, isbn_13) @number_10 = isbn_10 @number_13 = isbn_13 end def authority 'ISBN' end end ================================================ FILE: spec/rails_app/active_record/config.rb ================================================ # frozen_string_literal: true ActiveRecord::Base.configurations = { 'test' => { 'adapter' => 'sqlite3', 'database' => ':memory:' } } ActiveRecord::Base.establish_connection(:test) ================================================ FILE: spec/rails_app/active_record/models.rb ================================================ # frozen_string_literal: true class Post < ActiveRecord::Base has_one :user comma do title description user :full_name end end class User < ActiveRecord::Base comma do first_name last_name full_name 'Name' end comma :shortened do first_name last_name end def full_name "#{first_name} #{last_name}".strip end end class CreateTables < ActiveRecord::Migration[4.2] def self.up create_table :users do |t| t.string :first_name t.string :last_name t.timestamps end create_table :posts do |t| t.references :user t.string :title t.string :description t.timestamps end end def self.down drop_table :posts drop_table :users end end ActiveRecord::Migration.verbose = false CreateTables.up ================================================ FILE: spec/rails_app/data_mapper/config.rb ================================================ # frozen_string_literal: true DataMapper.setup(:default, 'sqlite::memory:') ================================================ FILE: spec/rails_app/mongoid/config.rb ================================================ # frozen_string_literal: true Mongoid.configure do |config| config.sessions = { default: { hosts: ['localhost:27017'], database: 'comma_test' } } end ================================================ FILE: spec/rails_app/rails_app.rb ================================================ # frozen_string_literal: true require 'action_controller/railtie' require 'action_view/railtie' # orm configs require 'rails_app/active_record/config' if defined?(ActiveRecord) app = CommaTestApp = Class.new(Rails::Application) app.config.secret_token = '6f6acf0443f74fd0aa8ff07a7c2fbe0a' app.config.session_store :cookie_store, key: '_rails_app_session' app.config.active_support.deprecation = :log app.config.eager_load = false app.config.root = File.dirname(__FILE__) Rails.backtrace_cleaner.remove_silencers! app.initialize! app.routes.draw do resources :users, only: [:index] get 'with_custom_options', to: 'users#with_custom_options' get 'with_custom_style', to: 'users#with_custom_style' root to: 'users#index' end # models require 'rails_app/active_record/models' if defined?(ActiveRecord) def is_rails_4? Rails::VERSION::STRING =~ /^4.*/ end if is_rails_4? def symbolize_param_keys(params) params.symbolize_keys end else def symbolize_param_keys(params) if params params.to_unsafe_h.symbolize_keys else {} end end end # controllers class ApplicationController < ActionController::Base; end class UsersController < ApplicationController def index respond_to do |format| format.html do if is_rails_4? render text: 'Users!' else render plain: 'Users!' end end format.csv { render csv: User.all } end end def with_custom_options render_options = { csv: User.all }.update(symbolize_param_keys(params[:custom_options])) respond_to do |format| format.csv { render render_options } end end def with_custom_style respond_to do |format| format.csv { render csv: User.all, style: :shortened } end end end # helpers Object.const_set(:ApplicationHelper, Module.new) ================================================ FILE: spec/rails_app/tmp/.gitkeep ================================================ ================================================ FILE: spec/spec_helper.rb ================================================ # frozen_string_literal: true require 'rubygems' $LOAD_PATH.unshift(File.expand_path(File.join('..', '..', 'lib'), __FILE__)) require 'simplecov' require 'coveralls' SimpleCov.formatter = Coveralls::SimpleCov::Formatter if defined? Rails SimpleCov.start('rails') do add_filter %r{^/spec/comma/rails/data_mapper_collection_spec\.rb$} add_filter %r{^/spec/comma/rails/mongoid_spec\.rb$} end else SimpleCov.start do add_filter %r{^/spec/comma/rails/data_mapper_collection_spec\.rb} add_filter %r{^/spec/comma/rails/mongoid_spec\.rb} add_filter %r{^/spec/controllers/} end end require 'bundler/setup' Bundler.require require 'rspec/active_model/mocks' require 'rspec/its' begin require 'rails' rescue LoadError warn 'rails not loaded' end %w[data_mapper mongoid active_record].each do |orm| begin require orm break rescue LoadError warn "#{orm} not loaded" end end if defined? Rails require 'rails_app/rails_app' require 'rspec/rails' else require 'rails_app/data_mapper/config' if defined?(DataMapper) require 'rails_app/mongoid/config' if defined?(Mongoid) require 'rails_app/active_record/config' if defined?(ActiveRecord) end Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |file| require file } require File.expand_path('../spec/non_rails_app/ruby_classes', __dir__)