class Lanes::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/lanes/api/controller_base.rb, line 27
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/lanes/api/controller_base.rb, line 181
def add_access_limits_to_query(query)
    if model.respond_to?(:access_limits_for_query)
        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/lanes/api/controller_base.rb, line 215
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/lanes/api/controller_base.rb, line 258
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.has_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/lanes/api/controller_base.rb, line 247
def add_scope_to_query(query)
    query_scopes.each do | name, arg |
        if model.has_exported_scope?(name, current_user)
            args = [name]
            args.push( arg ) unless arg.blank?
            query = query.send( *args )
        end
    end
    query
end
add_scopes_to_query(query) click to toggle source
# File lib/lanes/api/controller_base.rb, line 208
def add_scopes_to_query(query)
    if query_scopes.present?
        query = add_scope_to_query(query)
    end
    query
end
build_allowed_associations(association, model_class = self.model) click to toggle source
# File lib/lanes/api/controller_base.rb, line 153
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/lanes/api/controller_base.rb, line 189
def build_query(query = model.all)
    if params[:id]
        query = query.where(id: params[:id])
    end
    if params[:nested_attribute]
        query = query.where(params[:nested_attribute])
    end
    query = add_access_limits_to_query(query)

    if query_params.present?
        query = add_params_to_query(query)
    end
    query
end
build_reply_options() click to toggle source

Extract options that are suitable for use in 'as_json'

# File lib/lanes/api/controller_base.rb, line 138
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{|f| model.has_exported_method?(f, current_user) }
    end
    options[:format] = reply_with_array? ? 'array' : 'object'
    options
end
convert_field_to_arel(field) click to toggle source
# File lib/lanes/api/controller_base.rb, line 272
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/lanes/api/controller_base.rb, line 204
def count_query_records(query)
    model.from('('+query.to_sql+') q').count
end
current_user() click to toggle source
# File lib/lanes/api/controller_base.rb, line 36
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/lanes/api/controller_base.rb, line 290
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)
    else
        value =~ /%/ ? field.matches( value ) : field.eq( value )
    end
end
include_associations() click to toggle source
# File lib/lanes/api/controller_base.rb, line 116
def include_associations
    [*params[:i]]
end
max_query_results_size() click to toggle source
# File lib/lanes/api/controller_base.rb, line 243
def max_query_results_size
    250 # should be enough for everybody, amirite?
end
perform_multiple_destroy() click to toggle source
# File lib/lanes/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/lanes/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/lanes/api/controller_base.rb, line 40
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)
    if params[:id]
        query  = query.first!
    end
    std_api_reply(:retrieve, query, options)
end
perform_single_destroy() click to toggle source
# File lib/lanes/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/lanes/api/controller_base.rb, line 94
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/lanes/api/controller_base.rb, line 122
def query_limit_size
    limit = max_query_results_size
    params[:l] ? [ params[:l].to_i, limit ].min : limit
end
query_offset() click to toggle source
# File lib/lanes/api/controller_base.rb, line 126
def query_offset
    params[:s]
end
query_params() click to toggle source
# File lib/lanes/api/controller_base.rb, line 113
def query_params
    params[:q]
end
query_scopes() click to toggle source
# File lib/lanes/api/controller_base.rb, line 110
def query_scopes
    [*params[:w]]
end
reply_with_array?() click to toggle source
# File lib/lanes/api/controller_base.rb, line 107
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/lanes/api/controller_base.rb, line 104
def requested_fields
    [*params[:f]]
end
should_include_total_count?() click to toggle source

Should the result include the total number of available records

# File lib/lanes/api/controller_base.rb, line 133
def should_include_total_count?
    params[:l] && params[:s] && ! params[:id]
end
sort_order() click to toggle source
# File lib/lanes/api/controller_base.rb, line 119
def sort_order
    params[:o]
end