class AutoForme::Models::Sequel

  1. lib/autoforme/models/sequel.rb
Superclass: Model

Sequel specific model class for AutoForme

Constants

S = ::Sequel  

Short reference to top level Sequel module, for easily calling methods

SUPPORTED_ASSOCIATION_TYPES = [:many_to_one, :one_to_one, :one_to_many, :many_to_many]  

What association types to recognize. Other association types are ignored.

Public Class methods

new (*)

Make sure the forme plugin is loaded into the model.

[show source]
# File lib/autoforme/models/sequel.rb, line 13
def initialize(*)
  super
  model.plugin :forme
end

Public Instance methods

all_rows_for (type, request)

Retrieve all matching rows for this model.

[show source]
# File lib/autoforme/models/sequel.rb, line 128
def all_rows_for(type, request)
  all_dataset_for(type, request).all
end
apply_associated_eager (type, request, ds)

On the browse/search results pages, in addition to eager loading based on the current model's eager loading config, also eager load based on the associated models config.

[show source]
# File lib/autoforme/models/sequel.rb, line 203
def apply_associated_eager(type, request, ds)
  columns_for(type, request).each do |col|
    if association?(col)
      if model = associated_model_class(col)
        eager = model.eager_for(:association, request) || model.eager_graph_for(:association, request)
        ds = ds.eager(col=>eager)
      else
        ds = ds.eager(col)
      end
    end
  end
  ds
end
apply_dataset_options (type, request, ds)

Apply the model's filter, eager, and order to the given dataset

[show source]
# File lib/autoforme/models/sequel.rb, line 231
def apply_dataset_options(type, request, ds)
  ds = apply_filter(type, request, ds)
  if order = order_for(type, request)
    ds = ds.order(*order)
  end
  if eager = eager_for(type, request)
    ds = ds.eager(eager)
  end
  if eager_graph = eager_graph_for(type, request)
    ds = ds.eager_graph(eager_graph)
  end
  ds
end
apply_filter (type, request, ds)

Apply the model's filter to the given dataset

[show source]
# File lib/autoforme/models/sequel.rb, line 223
def apply_filter(type, request, ds)
  if filter = filter_for
    ds = filter.call(ds, type, request)
  end
  ds
end
associated_class (assoc)

The associated class for the given association

[show source]
# File lib/autoforme/models/sequel.rb, line 64
def associated_class(assoc)
  model.association_reflection(assoc).associated_class
end
associated_mtm_objects (request, assoc, obj)

The currently associated many to many objects for the association

[show source]
# File lib/autoforme/models/sequel.rb, line 305
def associated_mtm_objects(request, assoc, obj)
  ds = obj.send("#{assoc}_dataset")
  if assoc_class = associated_model_class(assoc)
    ds = assoc_class.apply_dataset_options(:association, request, ds)
  end
  ds
end
associated_new_column_values (obj, assoc)

An array of pairs mapping foreign keys in associated class to primary key value of current object

[show source]
# File lib/autoforme/models/sequel.rb, line 90
def associated_new_column_values(obj, assoc)
  ref = model.association_reflection(assoc)
  ref[:keys].zip(ref[:primary_keys].map{|k| obj.send(k)})
end
association? (column)

Whether the column represents an association.

[show source]
# File lib/autoforme/models/sequel.rb, line 54
def association?(column)
  case column
  when String
    model.associations.map(&:to_s).include?(column)
  else
    model.association_reflection(column)
  end
end
association_autocomplete? (assoc, request)

Whether to autocomplete for the given association.

[show source]
# File lib/autoforme/models/sequel.rb, line 246
def association_autocomplete?(assoc, request)
  (c = associated_model_class(assoc)) && c.autocomplete_options_for(:association, request)
end
association_key (assoc)

The foreign key column for the given many to one association.

[show source]
# File lib/autoforme/models/sequel.rb, line 84
def association_key(assoc)
  model.association_reflection(assoc)[:key]
end
association_names (types=SUPPORTED_ASSOCIATION_TYPES)

Array of association name strings for given association types

[show source]
# File lib/autoforme/models/sequel.rb, line 101
def association_names(types=SUPPORTED_ASSOCIATION_TYPES)
  model.all_association_reflections.select{|r| types.include?(r[:type])}.map{|r| r[:name]}.sort_by(&:to_s)
end
association_type (assoc)

A short type for the association, either :one for a singular association, :new for an association where you can create new objects, or :edit for association where you can add/remove members from the association.

[show source]
# File lib/autoforme/models/sequel.rb, line 72
def association_type(assoc)
  case model.association_reflection(assoc)[:type]
  when :many_to_one, :one_to_one
    :one
  when :one_to_many
    :new
  when :many_to_many
    :edit
  end
end
autocomplete (opts={})

Return array of autocompletion strings for the request. Options:

:type

Action type symbol

:request

AutoForme::Request instance

:association

Association symbol

:query

Query string submitted by the user

:exclude

Primary key value of current model, excluding already associated values (used when editing many to many associations)

[show source]
# File lib/autoforme/models/sequel.rb, line 257
def autocomplete(opts={})
  type, request, assoc, query, exclude = opts.values_at(:type, :request, :association, :query, :exclude)
  if assoc
    if exclude && association_type(assoc) == :edit
      ref = model.association_reflection(assoc)
      block = lambda do |ds|
        ds.exclude(S.qualify(ref.associated_class.table_name, ref.right_primary_key)=>model.db.from(ref[:join_table]).where(ref[:left_key]=>exclude).select(ref[:right_key]))
      end
    end
    return associated_model_class(assoc).autocomplete(opts.merge(:type=>:association, :association=>nil), &block)
  end
  opts = autocomplete_options_for(type, request)
  callback_opts = {:type=>type, :request=>request, :query=>query}
  ds = all_dataset_for(type, request)
  ds = opts[:callback].call(ds, callback_opts) if opts[:callback]
  display = opts[:display] || S.qualify(model.table_name, :name)
  display = display.call(callback_opts) if display.respond_to?(:call)
  limit = opts[:limit] || 10
  limit = limit.call(callback_opts) if limit.respond_to?(:call)
  opts[:filter] ||= lambda{|ds1, _| ds1.where(S.ilike(display, "%#{ds.escape_like(query)}%"))}
  ds = opts[:filter].call(ds, callback_opts)
  ds = ds.select(S.join([S.qualify(model.table_name, model.primary_key), display], ' - ').as(:v)).
    limit(limit)
  ds = yield ds if block_given?
  ds.map(:v)
end
base_class ()

The base class for the underlying model, ::Sequel::Model.

[show source]
# File lib/autoforme/models/sequel.rb, line 19
def base_class
  S::Model
end
browse (type, request, opts={})

Return array of matching objects for the current page.

[show source]
# File lib/autoforme/models/sequel.rb, line 181
def browse(type, request, opts={})
  paginate(type, request, apply_associated_eager(:browse, request, all_dataset_for(type, request)), opts)
end
column_type (column)

The schema type for the column

[show source]
# File lib/autoforme/models/sequel.rb, line 218
def column_type(column)
  (sch = model.db_schema[column]) && sch[:type]
end
default_columns ()

Return the default columns for this model

[show source]
# File lib/autoforme/models/sequel.rb, line 133
def default_columns
  columns = model.columns - Array(model.primary_key)
  model.all_association_reflections.each do |reflection|
    next unless reflection[:type] == :many_to_one
    if i = columns.index(reflection[:key])
      columns[i] = reflection[:name]
    end
  end
  columns.sort_by(&:to_s)
end
form_param_name (assoc)

The name of the form param for the given association.

[show source]
# File lib/autoforme/models/sequel.rb, line 24
def form_param_name(assoc)
  "#{model.send(:underscore, model.name)}[#{association_key(assoc)}]"
end
mtm_association_names ()

Array of many to many association name strings.

[show source]
# File lib/autoforme/models/sequel.rb, line 96
def mtm_association_names
  association_names([:many_to_many])
end
mtm_update (request, assoc, obj, add, remove)

Update the many to many association. add and remove should be arrays of primary key values of associated objects to add to the association.

[show source]
# File lib/autoforme/models/sequel.rb, line 286
def mtm_update(request, assoc, obj, add, remove)
  ref = model.association_reflection(assoc)
  assoc_class = associated_model_class(assoc)
  ret = nil
  model.db.transaction do
    [[add, ref.add_method], [remove, ref.remove_method]].each do |ids, meth|
      if ids
        ids.each do |id|
          next if id.to_s.empty?
          ret = assoc_class ? assoc_class.with_pk(:association, request, id) : obj.send(:_apply_association_options, ref, ref.associated_class.dataset.clone).with_pk!(id)
          obj.send(meth, ret)
        end
      end
    end
  end
  ret
end
paginate (type, request, ds, opts={})

Do very simple pagination, by selecting one more object than necessary, and noting if there is a next page by seeing if more objects are returned than the limit.

[show source]
# File lib/autoforme/models/sequel.rb, line 187
def paginate(type, request, ds, opts={})
  return ds.all if opts[:all_results]
  limit = limit_for(type, request)
  %r{\/(\d+)\z} =~ request.env['PATH_INFO']
  offset = (($1||1).to_i - 1) * limit
  objs = ds.limit(limit+1, (offset if offset > 0)).all
  next_page = false
  if objs.length > limit
    next_page = true
    objs.pop
  end
  [next_page, objs]
end
params_name ()

The namespace for form parameter names for this model, needs to match the ones automatically used by Forme.

[show source]
# File lib/autoforme/models/sequel.rb, line 118
def params_name
  model.send(:underscore, model.name)
end
primary_key_value (obj)

The primary key value for the given object.

[show source]
# File lib/autoforme/models/sequel.rb, line 112
def primary_key_value(obj)
  obj.pk
end
save (obj)

Save the object, returning the object if successful, or nil if not.

[show source]
# File lib/autoforme/models/sequel.rb, line 106
def save(obj)
  obj.raise_on_save_failure = false
  obj.save
end
search_results (type, request, opts={})

Returning array of matching objects for the current search page using the given parameters.

[show source]
# File lib/autoforme/models/sequel.rb, line 157
def search_results(type, request, opts={})
  params = request.params
  ds = apply_associated_eager(:search, request, all_dataset_for(type, request))
  columns_for(:search_form, request).each do |c|
    if (v = params[c.to_s]) && !v.empty?
      if association?(c)
        ref = model.association_reflection(c)
        ads = ref.associated_dataset
        if model_class = associated_model_class(c)
          ads = model_class.apply_filter(:association, request, ads)
        end
        primary_key = S.qualify(ref.associated_class.table_name, ref.primary_key)
        ds = ds.where(S.qualify(model.table_name, ref[:key])=>ads.where(primary_key=>v).select(primary_key))
      elsif column_type(c) == :string
        ds = ds.where(S.ilike(S.qualify(model.table_name, c), "%#{ds.escape_like(v.to_s)}%"))
      else
        ds = ds.where(S.qualify(model.table_name, c)=>v.to_s)
      end
    end
  end
  paginate(type, request, ds, opts)
end
session_value (column)

Add a filter restricting access to only rows where the column name matching the session value. Also add a before_create hook that sets the column value to the session value.

[show source]
# File lib/autoforme/models/sequel.rb, line 147
def session_value(column)
  filter do |ds, type, req|
    ds.where(S.qualify(model.table_name, column)=>req.session[column])
  end
  before_create do |obj, req|
    obj.send("#{column}=", req.session[column])
  end
end
set_fields (obj, type, request, params)

Set the fields for the given action type to the object based on the request params.

[show source]
# File lib/autoforme/models/sequel.rb, line 29
def set_fields(obj, type, request, params)
  columns_for(type, request).each do |col|
    column = col

    if association?(col)
      ref = model.association_reflection(col)
      ds = ref.associated_dataset
      if model_class = associated_model_class(col)
        ds = model_class.apply_filter(:association, request, ds)
      end

      v = params[ref[:key].to_s]
      v = nil if v.to_s.strip == ''
      if v
        v = ds.first!(S.qualify(ds.model.table_name, ref.primary_key)=>v)
      end
    else
      v = params[col.to_s]
    end

    obj.send("#{column}=", v)
  end
end
unassociated_mtm_objects (request, assoc, obj)

All objects in the associated table that are not currently associated to the given object.

[show source]
# File lib/autoforme/models/sequel.rb, line 314
def unassociated_mtm_objects(request, assoc, obj)
  ref = model.association_reflection(assoc)
  assoc_class = associated_model_class(assoc)
  lambda do |ds|
    subquery = model.db.from(ref[:join_table]).
      select(ref.qualified_right_key).
      where(ref.qualified_left_key=>obj.pk)
    ds = ds.exclude(S.qualify(ref.associated_class.table_name, ref.associated_class.primary_key)=>subquery)
    ds = assoc_class.apply_dataset_options(:association, request, ds) if assoc_class
    ds
  end
end
with_pk (type, request, pk)

Retrieve underlying model instance with matching primary key

[show source]
# File lib/autoforme/models/sequel.rb, line 123
def with_pk(type, request, pk)
  dataset_for(type, request).with_pk!(pk)
end