---
title: Object-Oriented File Importing and Parsing
teaser:
tags: web,ruby
author: Harlow Ward
published_on: 2012-10-11
---

The following is an example of file importing and parsing in Ruby using object-oriented techniques such as [duck typing](http://en.wikipedia.org/wiki/Duck_typing) and [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection).

Inject a `CSVParser` dependency into a the `Importer`

    # app/controllers/contact_imports_controller.rb:
    class ContactImportsController < ApplicationController
      def new
        @importer = Importer.new
      end

      def create
        @importer = Importer.new(
          parser: CSVParser.new(import_file),
          import_type: import_type
        )
        @importer.import

        respond_with(@importer, location: contacts_path)
      end

      private

      def import_file
        params[:importer][:file]
      end

      def import_type
        params[:importer][:import_type]
      end
    end

A given `parser` must respond to `rows` and an instance of the given `import_type` must respond to `create`

    # app/models/importer.rb
    class Importer
      include ActiveModel::Validations
      include ActiveModel::Conversion

      VALID_IMPORT_TYPES = ['contact']

      validates :import_type, inclusion: { in: VALID_IMPORT_TYPES }

      attr_reader :parser, :import_type

      def initialize(attributes={})
        @parser = attributes[:parser]
        @import_type = attributes[:import_type]
      end

      def import
        if valid?
          rows.each do |row|
            import_factory.create(row)
          end
        end
      end

      private

      def rows
        parser.rows
      end

      def import_factory
        import_type.to_s.constantize
      end
    end

A `Contact` responds to `create`:

    # app/models/contact.rb:
    class Contact < ActiveRecord::Base
    end

A `CSVParser` responds to `rows`:

    # app/models/csv_parser.rb:
    class CSVParser
      initialize(csv_file)
        @csv_file = csv_file
      end

      def rows
        # code that parses the CSV file and returns an array of hashes
      end
    end

To import <abbr title="Extensible Markup Language">XML</abbr> files instead of CSV files, replace the CSV parser with another object that responds to `rows`.

    # app/models/xml_parser.rb
    class XMLParser
      def initialize(xml_file)
        @xml_file = xml_file
      end

      def rows
        # code that parses the XML file and returns an array of hashes
      end
    end

### Takeaways

+ Create small objects with single responsibilities
+ Keep public interfaces consistant
+ Send messages between collaborating objects without querying  type

### What's next

Detect emerging problems in your codebase with [Ruby Science][ruby-science]. We'll deliver solutions for fixing them, and demonstrate techniques for building a Ruby on Rails application that will be fun to work on for years to come.

[Grab a free sample of Ruby Science today!][ruby-science]

[ruby-science]: http://rubyscience.com?utm_source=giantrobots&amp;utm_medium=blog&amp;utm_campaign=remarket&amp;utm_term=testing
