class Brainstem::PresenterCollection

Attributes

default_max_filter_and_search_page[RW]
default_max_per_page[RW]

@!attribute default_max_per_page @return [Integer] The maximum number of objects that can be requested in a single presented hash.

default_per_page[RW]

@!attribute default_per_page @return [Integer] The default number of objects that will be returned in the presented hash.

Public Class Methods

new() click to toggle source

@!visibility private

# File lib/brainstem/presenter_collection.rb, line 18
def initialize
  @default_per_page = 20
  @default_max_per_page = 200
  @default_max_filter_and_search_page = 10_000 # TODO: figure out a better default and make it configurable
end

Public Instance Methods

add_presenter_class(presenter_class, *klasses) click to toggle source

@param [String, Class] presenter_class The presenter class that knows how to present all of the classes given in klasses. @param [*Class] klasses One or more classes that can be presented by presenter_class.

# File lib/brainstem/presenter_collection.rb, line 123
def add_presenter_class(presenter_class, *klasses)
  klasses.each do |klass|
    presenters[klass.to_s] = presenter_class
  end
end
brainstem_key_for!(klass) click to toggle source
# File lib/brainstem/presenter_collection.rb, line 140
def brainstem_key_for!(klass)
  presenter = presenters[klass.to_s]
  raise(ArgumentError, "Unable to find a presenter for class #{klass}") unless presenter
  presenter.configuration[:brainstem_key] || klass.table_name
end
for(klass) click to toggle source

@return [Brainstem::Presenter, nil] A new instance of the Presenter that knows how to present the class klass, or nil if there isn't one.

# File lib/brainstem/presenter_collection.rb, line 130
def for(klass)
  presenters[klass.to_s].try(:new)
end
for!(klass) click to toggle source

@return [Brainstem::Presenter] A new instance of the Presenter that knows how to present the class klass. @raise [ArgumentError] if there is no known Presenter for klass.

# File lib/brainstem/presenter_collection.rb, line 136
def for!(klass)
  self.for(klass) || raise(ArgumentError, "Unable to find a presenter for class #{klass}")
end
presenters() click to toggle source

@return [Hash] The presenters this collection knows about, keyed on the names of the classes that can be presented.

# File lib/brainstem/presenter_collection.rb, line 117
def presenters
  @presenters ||= {}
end
presenting(name, options = {}, &block) click to toggle source

The main presentation method, converting a model name and an optional scope into a hash structure, ready to be converted into JSON. If searching, Brainstem filtering, only, pagination, and ordering are skipped and should be implemented with your search solution. All request options are passed to the +search block+ for your convenience. @param [Class, String] name Either the ActiveRecord Class itself, or its pluralized table name as a string. @param [Hash] options The options that will be applied as the objects are converted. @option options [Hash] :params The params hash included in a request for the presented object. @option options [ActiveRecord::Base] :model The model that is being presented (if different from name). @option options [Integer] :max_per_page The maximum number of items that can be requested by params[:per_page]. @option options [Integer] :per_page The number of items that will be returned if params[:per_page] is not set. @option options [Boolean] :apply_default_filters Determine if Presenter's filter defaults should be applied. On by default. @option options [Brainstem::Presenter] :primary_presenter The Presenter to use for filters and sorts. If unspecified, the :model or name will be used to find an appropriate Presenter. @yield Must return a scope on the model name, which will then be presented. @return [Hash] A hash of arrays of hashes. Top-level hash keys are pluralized model names, with values of arrays containing one hash per object that was found by the given given options.

# File lib/brainstem/presenter_collection.rb, line 37
def presenting(name, options = {}, &block)
  options[:params] = ActiveSupport::HashWithIndifferentAccess.new(options[:params] || {})
  check_for_old_options!(options)
  set_default_filters_option!(options)
  presented_class = (options[:model] || name)
  presented_class = presented_class.classify.constantize if presented_class.is_a?(String)
  scope = presented_class.instance_eval(&block)
  count = 0

  # grab the presenter that knows about filters and sorting etc.
  options[:primary_presenter] ||= for!(presented_class)

  # table name will be used to query the database for the filtered data
  options[:table_name] = presented_class.table_name

  options[:default_per_page] = default_per_page
  options[:default_max_per_page] = default_max_per_page
  options[:default_max_filter_and_search_page] = default_max_filter_and_search_page

  strategy = get_strategy(options, scope)
  primary_models, count = strategy.execute(scope)

  # Determine if an exception should be raised on an empty result set.
  if options[:raise_on_empty] && primary_models.empty?
    raise options[:empty_error_class] || ActiveRecord::RecordNotFound
  end

  structure_response(presented_class, primary_models, strategy, count, options)
end
structure_response(presented_class, primary_models, strategy, count, options) click to toggle source
# File lib/brainstem/presenter_collection.rb, line 67
def structure_response(presented_class, primary_models, strategy, count, options)
  # key these models will use in the struct that is output
  brainstem_key = brainstem_key_for!(presented_class)

  # filter the incoming :includes list by those available from this Presenter in the current context
  selected_associations = filter_includes(options)

  optional_fields = filter_optional_fields(options)
  page_size = strategy.calculate_per_page

  struct = {
    'count' => count,
    'results' => [],
    brainstem_key => {},
    'meta' => {
      'count' => count,
      'page_count' => count > 0 ? (count.to_f / page_size).ceil : 0,
      'page_number' => count > 0 ? options[:params].fetch(:page, 1).to_i : 0,
      'page_size' => page_size,
    }
  }

  # Build top-level keys for all requested associations.
  selected_associations.each do |association|
    struct[brainstem_key_for!(association.target_class)] ||= {} unless association.polymorphic?
  end

  if primary_models.length > 0
    associated_models = {}
    presented_primary_models = options[:primary_presenter].group_present(primary_models,
                                                                         selected_associations.map(&:name),
                                                                         optional_fields: optional_fields,
                                                                         load_associations_into: associated_models)

    struct[brainstem_key] = presented_primary_models.each.with_object({}) { |model, obj| obj[model['id']] = model }
    struct['results'] = presented_primary_models.map { |model| { 'key' => brainstem_key, 'id' => model['id'] } }

    associated_models.each do |association_brainstem_key, associated_models_hash|
      presenter = for!(associated_models_hash.values.first.class)
      struct[association_brainstem_key] ||= {}
      presenter.group_present(associated_models_hash.values).each do |model|
        struct[association_brainstem_key][model['id']] ||= model
      end
    end
  end

  struct
end
validate!() click to toggle source

@raise [StandardError] if any presenter in this collection is invalid.

# File lib/brainstem/presenter_collection.rb, line 147
def validate!
  errors = []
  presenters.each do |name, klass|
    validator = Brainstem::PresenterValidator.new(klass)
    unless validator.valid?
      errors += validator.errors.full_messages.map { |error| "#{name}: #{error}" }
    end
  end
  raise "PresenterCollection invalid:\n - #{errors.join("\n - ")}" if errors.length > 0
end

Private Instance Methods

check_for_old_options!(options) click to toggle source
# File lib/brainstem/presenter_collection.rb, line 193
def check_for_old_options!(options)
  if options[:as].present?
    raise "PresenterCollection#presenting no longer accepts the :as option.  Use the brainstem_key annotation in your presenters instead."
  end
end
filter_includes(options) click to toggle source
# File lib/brainstem/presenter_collection.rb, line 171
def filter_includes(options)
  allowed_associations = options[:primary_presenter].allowed_associations(options[:params][:only].present?)

  [].tap do |selected_associations|
    (options[:params][:include] || '').split(',').each do |k|
      if association = allowed_associations[k]
        selected_associations << association
      end
    end
  end
end
filter_optional_fields(options) click to toggle source
# File lib/brainstem/presenter_collection.rb, line 183
def filter_optional_fields(options)
  options[:params][:optional_fields].to_s.split(',').map(&:strip) & options[:primary_presenter].configuration[:fields].keys
end
get_strategy(options, scope) click to toggle source
# File lib/brainstem/presenter_collection.rb, line 160
def get_strategy(options, scope)
  strat = options[:primary_presenter].get_query_strategy

  return Brainstem::QueryStrategies::FilterAndSearch.new(options) if strat == :filter_and_search && searching?(options)
  return Brainstem::QueryStrategies::FilterOrSearch.new(options)
end
searching?(options) click to toggle source
# File lib/brainstem/presenter_collection.rb, line 167
def searching?(options)
  options[:params][:search] && options[:primary_presenter].configuration[:search].present?
end
set_default_filters_option!(options) click to toggle source
# File lib/brainstem/presenter_collection.rb, line 187
def set_default_filters_option!(options)
  return unless options[:params].has_key?(:apply_default_filters)

  options[:apply_default_filters] = [true, "true", "TRUE", 1, "1"].include? options[:params].delete(:apply_default_filters)
end