littlelines/ceo

View on GitHub
lib/ceo/iterator.rb

Summary

Maintainability
A
1 hr
Test Coverage
module CEO
  # Public: Delegates pagination and filters attributes.
  #
  # Examples
  #
  #   iterator = CEO::Iterator.new(Apple, current_page: 2, filters: { only: [:id, :name] })
  #   iterator.total_pages
  #   # => 7
  #   iterator.current_page
  #   # => 2
  class Iterator
    # Public: Returns the total pages paginated.
    attr_reader :total_pages

    # model - The model to perform queries on.
    # options - A hash of options for the iterator.
    #   query - An array of nested queries.
    #   filters - A hash of filters (only/except).
    #   current_page - The current page to be paginated.
    def initialize(model, options = {})
      @model = model
      @options = options
      @queries = @options[:query] || []
      @filters = @options[:filters] || {}
    end

    # Public: Iterates through all parts.
    #
    # Yields or returns an Enumerator.
    def each
      if block_yielded?
        all.each do |thing|
          yield(thing)
        end
      else
        all.to_enum
      end
    end

    # Public: Returns a hash of titleized attributes mapped to their values.
    #
    # Uses pagination.
    #
    # options -
    #   current_page - currently paginated page
    #   per_page     - # of things to list per page
    #
    # Returns a paginated hash of data.
    def all
      attribute_maps = [] # [{...}, {...}]
      @pages = CEO::Paginator.new(
        @model,
        current_page: current_page,
        per_page: @options.fetch(:per_page, 20)
      )

      self.total_pages = @pages.total_pages

      @pages.each do |thing|
        attr_object = {}

        # TODO: Make all of this into a method.
        # map first-level values to a hash
        keys.each do |key|
          attr_object[self.class.acronymize(key)] = thing[key.to_s]
        end

        # map nested values to a hash
        @queries.each do |query|
          attr_object.merge! query_eval(thing, query)
        end
        attribute_maps << attr_object
      end
      attribute_maps
    end

    # { 'Country Name' => 'South Korea' }
    def query_eval(scope, query)
      query_parts = query.split('.')
      if query_parts.length > 2
        title = self.class.acronymize(query_parts[-2..-1].join(' '))
        resp = 'None' if scope.instance_eval(query_parts[0]).nil? || scope.instance_eval(query_parts[0..1].join('.')).nil?
      elsif query_parts[-1] == 'name'
        title = self.class.acronymize(query_parts.join(' '))
        resp = 'None' if scope.instance_eval(query_parts[0]).nil?
      else
        title = self.class.acronymize query_parts[-1]
        resp = 'None' if scope.instance_eval(query_parts[0]).nil?
      end

      resp = scope.instance_eval(query) unless resp == 'None'
      { title => resp }
    end

    # Public: Filters an enum based on a hash of params.
    #
    # things  - An enum of things to filter.
    # filters - A hash of filters (ALLOWED: [:only, :except]).
    #
    # Since 'only' and 'except' are mutually exclusive, 'only'
    # will be prefered over 'except'.
    #
    # Returns a hash of filtered keys.
    def self.filter(things, filters)
      return self.only(things, filters[:only]) unless filters[:only].nil? || filters[:only].empty?
      return self.except(things, filters[:except]) unless filters[:except].nil? || filters[:except].empty?

      return things # if nothing to filter, just return the things
    end

    # Public: Blacklists keys based on an array.
    #
    # things - An array of keys to be filtered.
    # blacklist - An array of keys that are not allowed.
    #
    # Examples
    #
    #   blacklist = [:this, :that]
    #   except([:this, :that, :here, :now], blacklist)
    #   # => [:here, :now]
    #
    # Returns a filtered hash of keys.
    def self.except(things, blacklist)
      blacklist = blacklist.map(&:to_s)
      things = things.map(&:to_s)
      things.select { |thing| !blacklist.include? thing }
    end

    # Public: Whitelists keys based on an array.
    #
    # things - An array of keys to be filtered.
    # whitelist - An array of keys that are only allowed.
    #
    # Examples
    #
    #   whitelist = [:this, :that]
    #   only([:this, :that, :here, :now], whitelist)
    #   # => [:this, :that]
    #
    # Returns a filtered hash of keys.
    def self.only(things, whitelist)
      whitelist = whitelist.map(&:to_s)
      things = things.map(&:to_s)
      things.select { |thing| whitelist.include? thing }
    end

    # Public: Returns the current page.
    def current_page
      (@options[:current_page] || 1).to_i
    end

    # Public: Titleizes normal stuff, but upcases acronyms.
    def self.acronymize(key)
      parsed_key = key.to_s.titleize
      parsed_key = parsed_key.gsub 'Id', 'ID'
      parsed_key.gsub 'Iata Code', 'IATA'
    end

    private

    attr_writer :total_pages

    def keys
      singleton_class.send(:filter, @model.new.attributes.keys, @filters)
    end
  end
end