class Hippo::API::ControllerBase

The Controller handles querying models using either pre-defined scopes or hash based queries; and also including optional associations with the reply

It assigns the following meaning the these parameters.

* f: (fields)   Include the following fields (usually methods) with the reply
* w: (with)     Uses the defined scope to query and/or add extra data to the model
* q: (query)    Query the model using fields and values
     it is an array of clauses, which can be either forms
     { field: value }, or { field: { op: 'like', value: 'value%' } }
* i: (include)  Include associations along with the model in the reply
* o: (order)    Order by, { field => "ASC|DESC" }
* l: (limit)    Limit the returned rows to the count
* s: (start)    Start the query at the given offset (for paging)
* df: (data format) Should data be returned as 'object' (default) or 'array'

The parameters are deliberately shortened so they can be used in query parameters without blowing the URL up to an unacceptable length

Attributes

data[R]
model[R]
params[R]

Public Class Methods

new(model, authentication, params, data={}) click to toggle source
# File lib/hippo/api/controller_base.rb, line 29
def initialize(model, authentication, params, data={})
    @model  = model
    @params = params
    @data   = data
    @authentication = authentication
end

Protected Instance Methods

add_access_limits_to_query(query) click to toggle source

query options

# File lib/hippo/api/controller_base.rb, line 185
def add_access_limits_to_query(query)
    if model.respond_to?(:access_limits_for_query)
        model.access_limits_for_query(query, current_user, params)
    else
        query
    end
end
add_modifiers_to_query(query) click to toggle source
# File lib/hippo/api/controller_base.rb, line 212
def add_modifiers_to_query(query)
    query = query.limit(query_limit_size)
    query = query.offset(query_offset.to_i) if query_offset.present?

    if include_associations.any?
        allowed_includes = include_associations.each_with_object([]) do |desired, results|
            if desired.is_a?(Hash)
                nested = {}
                desired.each do |name, sub_associations|
                    nested[name.to_sym] = sub_associations if model.has_exported_association?(name, current_user)
                end
                results.push(nested) unless nested.empty?
            else
                results.push(desired.to_sym) if model.has_exported_association?(desired, current_user)
            end
        end
        query = query.includes(allowed_includes) unless allowed_includes.empty?
    end
    if sort_order.present?
        sort_order.each do |fld, dir|
            query = model.append_sort_to_query(
                query, fld, dir.downcase.to_sym
            )
        end
    end
    query
end
add_params_to_query(query) click to toggle source
# File lib/hippo/api/controller_base.rb, line 254
def add_params_to_query(query)
    query_params.each do |field, value|
        next unless (field = convert_field_to_arel(field))
        if value.is_a?(Hash) && value.key?('value')
            op = value['op']
            value = value['value']
        end
        query = query.where(
            field_to_predicate(field, value, op)
        )
    end
    query
end
add_scope_to_query(query) click to toggle source
# File lib/hippo/api/controller_base.rb, line 244
def add_scope_to_query(query)
    query_scopes.each do |name, arg|
        next unless model.has_exported_scope?(name, current_user)
        args = [name]
        args.push(arg) unless arg.blank?
        query = query.send(*args)
    end
    query
end
add_scopes_to_query(query) click to toggle source
# File lib/hippo/api/controller_base.rb, line 207
def add_scopes_to_query(query)
    query = add_scope_to_query(query) if query_scopes.present?
    query
end
build_allowed_associations(association, model_class = self.model) click to toggle source
# File lib/hippo/api/controller_base.rb, line 156
def build_allowed_associations(association, model_class = self.model)
    includes = {}
    if association.is_a?(Hash)
        association.each do |include_name, sub_associations|
            if model_class.has_exported_association?(include_name, current_user) &&
               (reflection = model_class.reflect_on_association(include_name.to_sym))

                sub_includes = includes[include_name.to_sym] = {}
                allowed = build_allowed_associations(sub_associations, reflection.klass)
                unless allowed.empty?
                    sub_includes[:include] ||= []
                    sub_includes[:include] << allowed
                end
            end
        end
    elsif association.is_a?(Array)
        association.each do |sub_association|
            if model_class.has_exported_association?(sub_association, current_user)
                includes.merge! build_allowed_associations(sub_association, model_class)
            end
        end
    else
        includes[association.to_sym] = {} if  model_class.has_exported_association?(association, current_user)
    end
    includes
end
build_query(query = model.all) click to toggle source
# File lib/hippo/api/controller_base.rb, line 193
def build_query(query = model.all)
    query = query.where(id: params[:id]) if params[:id]
    if params[:nested_attribute]
        query = query.where(params[:nested_attribute])
    end
    query = add_access_limits_to_query(query)
    query = add_params_to_query(query) if query_params.present?
    query
end
build_reply_options() click to toggle source

Extract options that are suitable for use in 'as_json'

# File lib/hippo/api/controller_base.rb, line 139
def build_reply_options
    options = {}
    if include_associations.any?
        options[:include] = include_associations.each_with_object({}) do |association, includes|
            includes.merge! build_allowed_associations(association)
        end
    end

    if requested_fields.any?
        options[:methods] = requested_fields.select do |f|
            model.has_exported_method?(f, current_user)
        end
    end
    options[:format] = reply_with_array? ? 'array' : 'object'
    options
end
convert_field_to_arel(field) click to toggle source
# File lib/hippo/api/controller_base.rb, line 268
def convert_field_to_arel(field)
    if field.include?('.')
        (table_name, field_name) = field.split('.')
        if model.has_exported_join_table?(table_name, current_user)
            Arel::Table.new(table_name)[field_name]
        else
            nil
        end
    elsif model.attribute_method?(field)
        model.arel_table[field]
    else
        Arel::Nodes::SqlLiteral.new(
            model.connection.quote_column_name(field)
        )
    end
end
count_query_records(query) click to toggle source
# File lib/hippo/api/controller_base.rb, line 203
def count_query_records(query)
    query.unscope(:select).count
end
current_user() click to toggle source
# File lib/hippo/api/controller_base.rb, line 38
def current_user
    @current_user ||= @authentication.current_user
end
field_to_predicate(field, value, op = nil) click to toggle source

complete list: github.com/rails/arel/blob/master/lib/arel/predications.rb

# File lib/hippo/api/controller_base.rb, line 286
def field_to_predicate(field, value, op = nil)
    case op
    when nil, 'eq' then field.eq(value)
    when 'like' then field.matches(value)
    when 'ne'   then field.not_eq(value)
    when 'lt'   then field.lt(value)
    when 'in'   then field.in(Range.new(*value))
    when 'gt'   then field.gt(value)
    when 'between' then field.between(Range.new(*value.split('...')))
    else
        value =~ /%/ ? field.matches(value) : field.eq(value)
    end
end
include_associations() click to toggle source
# File lib/hippo/api/controller_base.rb, line 114
def include_associations
    [*params[:i]]
end
max_query_results_size() click to toggle source
# File lib/hippo/api/controller_base.rb, line 240
def max_query_results_size
    250 # should be enough for everybody, amirite?
end
perform_multiple_destroy() click to toggle source
# File lib/hippo/api/controller_base.rb, line 63
def perform_multiple_destroy
    query = model.where(id: data.map { |rec| rec['id'] })
    query = add_access_limits_to_query(query)
    success = true
    query.each do |record|
        if current_user.can_delete?(record, record.id)
            success = false unless record.destroy
        end
    end
    options = build_reply_options.merge(success: success)
    std_api_reply(:destroy, query, options)
end
perform_multiple_updates() click to toggle source
# File lib/hippo/api/controller_base.rb, line 76
def perform_multiple_updates
    query = model.where(id: data.map { |rec| rec['id'] })
    query = add_access_limits_to_query(query)
    success = true
    query.each do |record|
        record_data = data.detect { |rd| rd['id'] == record.id }
        next unless record_data
        if current_user.can_write?(record, record.id)
            record.set_attribute_data(record_data, current_user)
            success = false unless record.save
        end
    end
    options = build_reply_options.merge(success: success)
    std_api_reply(:update, query, options)
end
perform_retrieval() click to toggle source
# File lib/hippo/api/controller_base.rb, line 42
def perform_retrieval
    query   = build_query
    query   = add_scopes_to_query(query)
    query   = add_access_limits_to_query(query)
    options = build_reply_options
    if should_include_total_count?
        options[:total_count] = count_query_records(query)
    end
    query = add_modifiers_to_query(query)
    query = query.first! if params[:id]
    std_api_reply(:retrieve, query, options)
end
perform_single_destroy() click to toggle source
# File lib/hippo/api/controller_base.rb, line 55
def perform_single_destroy
    query = model.where(id: params[:id])
    query = add_access_limits_to_query(query)
    record = query.first!
    record.destroy
    std_api_reply(:destroy, record, {})
end
perform_single_update() click to toggle source
# File lib/hippo/api/controller_base.rb, line 92
def perform_single_update
    query = build_query
    query = add_access_limits_to_query(query)
    record = query.first!
    record.set_attribute_data(data, current_user)
    options = build_reply_options.merge(success: record.save)
    std_api_reply(:update, record, options)
end
query_limit_size() click to toggle source
# File lib/hippo/api/controller_base.rb, line 120
def query_limit_size
    limit = max_query_results_size
    requested_limit ? [requested_limit, limit].min : limit
end
query_offset() click to toggle source
# File lib/hippo/api/controller_base.rb, line 124
def query_offset
    params[:s]
end
query_params() click to toggle source
# File lib/hippo/api/controller_base.rb, line 111
def query_params
    params[:q]
end
query_scopes() click to toggle source
# File lib/hippo/api/controller_base.rb, line 108
def query_scopes
    [*params[:w]]
end
reply_with_array?() click to toggle source
# File lib/hippo/api/controller_base.rb, line 105
def reply_with_array?
    params[:df] == 'array'
end
requested_fields() click to toggle source

@return [Array<String>] The fields to include in query. May represent either an attribute or a method

# File lib/hippo/api/controller_base.rb, line 102
def requested_fields
    [*params[:f]]
end
requested_limit() click to toggle source
# File lib/hippo/api/controller_base.rb, line 127
def requested_limit
    params.key?(:l) ? params[:l].to_i : nil
end
should_include_total_count?() click to toggle source

Should the result include the total number of available records

# File lib/hippo/api/controller_base.rb, line 134
def should_include_total_count?
    requested_limit && params[:id].blank?
end
sort_order() click to toggle source
# File lib/hippo/api/controller_base.rb, line 117
def sort_order
    params[:o]
end