Methods
Public Class
Public Instance
- all_rows_for
- apply_associated_eager
- apply_dataset_options
- apply_filter
- associated_class
- associated_mtm_objects
- associated_new_column_values
- association?
- association_autocomplete?
- association_key
- association_names
- association_type
- autocomplete
- base_class
- browse
- column_type
- default_columns
- editable_mtm_association_names
- form_param_name
- mtm_association_names
- mtm_update
- paginate
- params_name
- primary_key_value
- save
- search_results
- session_value
- set_fields
- unassociated_mtm_objects
- with_pk
Constants
S | = | ::Sequel |
Short reference to top level |
|
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. |
Attributes
params_name | [R] |
The namespace for form parameter names for this model, needs to match the ones automatically used by Forme. |
Public Class methods
Make sure the forme plugin is loaded into the model.
# File lib/autoforme/models/sequel.rb 18 def initialize(*) 19 super 20 model.plugin :forme 21 @params_name = model.new.forme_namespace 22 end
Public Instance methods
Retrieve all matching rows for this model.
# File lib/autoforme/models/sequel.rb 140 def all_rows_for(type, request) 141 all_dataset_for(type, request).all 142 end
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.
# File lib/autoforme/models/sequel.rb 222 def apply_associated_eager(type, request, ds) 223 columns_for(type, request).each do |col| 224 if association?(col) 225 if model = associated_model_class(col) 226 eager = model.eager_for(:association, request) || model.eager_graph_for(:association, request) 227 ds = ds.eager(col=>eager) 228 else 229 ds = ds.eager(col) 230 end 231 end 232 end 233 ds 234 end
Apply the model’s filter, eager, and order to the given dataset
# File lib/autoforme/models/sequel.rb 250 def apply_dataset_options(type, request, ds) 251 ds = apply_filter(type, request, ds) 252 if order = order_for(type, request) 253 ds = ds.order(*order) 254 end 255 if eager = eager_for(type, request) 256 ds = ds.eager(eager) 257 end 258 if eager_graph = eager_graph_for(type, request) 259 ds = ds.eager_graph(eager_graph) 260 end 261 ds 262 end
Apply the model’s filter to the given dataset
# File lib/autoforme/models/sequel.rb 242 def apply_filter(type, request, ds) 243 if filter = filter_for 244 ds = filter.call(ds, type, request) 245 end 246 ds 247 end
The associated class for the given association
# File lib/autoforme/models/sequel.rb 70 def associated_class(assoc) 71 model.association_reflection(assoc).associated_class 72 end
The currently associated many to many objects for the association
# File lib/autoforme/models/sequel.rb 334 def associated_mtm_objects(request, assoc, obj) 335 ds = obj.send("#{assoc}_dataset") 336 if assoc_class = associated_model_class(assoc) 337 ds = assoc_class.apply_dataset_options(:association, request, ds) 338 end 339 ds 340 end
An array of pairs mapping foreign keys in associated class to primary key value of current object
# File lib/autoforme/models/sequel.rb 96 def associated_new_column_values(obj, assoc) 97 ref = model.association_reflection(assoc) 98 ref[:keys].zip(ref[:primary_keys].map{|k| obj.send(k)}) 99 end
Whether the column represents an association.
# File lib/autoforme/models/sequel.rb 60 def association?(column) 61 case column 62 when String 63 model.associations.map(&:to_s).include?(column) 64 else 65 model.association_reflection(column) 66 end 67 end
Whether to autocomplete for the given association.
# File lib/autoforme/models/sequel.rb 265 def association_autocomplete?(assoc, request) 266 (c = associated_model_class(assoc)) && c.autocomplete_options_for(:association, request) 267 end
The foreign key column for the given many to one association.
# File lib/autoforme/models/sequel.rb 90 def association_key(assoc) 91 model.association_reflection(assoc)[:key] 92 end
Array of association name strings for given association types. If a block is given, only include associations where the block returns truthy.
# File lib/autoforme/models/sequel.rb 116 def association_names(types=SUPPORTED_ASSOCIATION_TYPES) 117 model.all_association_reflections. 118 select{|r| types.include?(r[:type]) && (!block_given? || yield(r))}. 119 map{|r| r[:name]}. 120 sort_by(&:to_s) 121 end
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.
# File lib/autoforme/models/sequel.rb 78 def association_type(assoc) 79 case model.association_reflection(assoc)[:type] 80 when :many_to_one, :one_to_one 81 :one 82 when :one_to_many 83 :new 84 when :many_to_many 85 :edit 86 end 87 end
Return array of autocompletion strings for the request. Options:
:type |
|
:request |
|
: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) |
# File lib/autoforme/models/sequel.rb 276 def autocomplete(opts={}) 277 type, request, assoc, query, exclude = opts.values_at(:type, :request, :association, :query, :exclude) 278 if assoc 279 if exclude && association_type(assoc) == :edit 280 ref = model.association_reflection(assoc) 281 block = lambda do |ds| 282 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])) 283 end 284 end 285 return associated_model_class(assoc).autocomplete(opts.merge(:type=>:association, :association=>nil), &block) 286 end 287 opts = autocomplete_options_for(type, request) 288 callback_opts = {:type=>type, :request=>request, :query=>query} 289 ds = all_dataset_for(type, request) 290 ds = opts[:callback].call(ds, callback_opts) if opts[:callback] 291 display = opts[:display] || S.qualify(model.table_name, :name) 292 display = display.call(callback_opts) if display.respond_to?(:call) 293 limit = opts[:limit] || 10 294 limit = limit.call(callback_opts) if limit.respond_to?(:call) 295 opts[:filter] ||= lambda{|ds1, _| ds1.where(S.ilike(display, "%#{ds.escape_like(query)}%"))} 296 ds = opts[:filter].call(ds, callback_opts) 297 ds = ds.select(S.join([S.qualify(model.table_name, model.primary_key), display], ' - ').as(:v)). 298 limit(limit) 299 ds = yield ds if block_given? 300 ds.map(:v) 301 end
The base class for the underlying model, ::Sequel::Model.
# File lib/autoforme/models/sequel.rb 25 def base_class 26 S::Model 27 end
Return array of matching objects for the current page.
# File lib/autoforme/models/sequel.rb 200 def browse(type, request, opts={}) 201 paginate(type, request, apply_associated_eager(:browse, request, all_dataset_for(type, request)), opts) 202 end
The schema type for the column
# File lib/autoforme/models/sequel.rb 237 def column_type(column) 238 (sch = model.db_schema[column]) && sch[:type] 239 end
Return the default columns for this model
# File lib/autoforme/models/sequel.rb 145 def default_columns 146 columns = model.columns - Array(model.primary_key) 147 model.all_association_reflections.each do |reflection| 148 next unless reflection[:type] == :many_to_one 149 if i = columns.index(reflection[:key]) 150 columns[i] = reflection[:name] 151 end 152 end 153 columns.sort! 154 end
Array of many to many association name strings for editable many to many associations.
# File lib/autoforme/models/sequel.rb 103 def editable_mtm_association_names 104 association_names([:many_to_many]) do |r| 105 model.method_defined?(r.add_method) && model.method_defined?(r.remove_method) 106 end 107 end
The name of the form param for the given association.
# File lib/autoforme/models/sequel.rb 30 def form_param_name(assoc) 31 "#{params_name}[#{association_key(assoc)}]" 32 end
Array of many to many association name strings.
# File lib/autoforme/models/sequel.rb 110 def mtm_association_names 111 association_names([:many_to_many]) 112 end
Update the many to many association. add and remove should be arrays of primary key values of associated objects to add to the association.
# File lib/autoforme/models/sequel.rb 305 def mtm_update(request, assoc, obj, add, remove) 306 ref = model.association_reflection(assoc) 307 assoc_class = associated_model_class(assoc) 308 ret = nil 309 model.db.transaction do 310 [[add, ref.add_method], [remove, ref.remove_method]].each do |ids, meth| 311 if ids 312 ids.each do |id| 313 next if id.to_s.empty? 314 ret = assoc_class ? assoc_class.with_pk(:association, request, id) : obj.send(:_apply_association_options, ref, ref.associated_class.dataset.clone).with_pk!(id) 315 begin 316 model.db.transaction(:savepoint=>true){obj.send(meth, ret)} 317 rescue S::UniqueConstraintViolation 318 # Already added, safe to ignore 319 rescue S::ConstraintViolation 320 # Old versions of sqlite3 and jdbc-sqlite3 can raise generic 321 # ConstraintViolation instead of UniqueConstraintViolation 322 # :nocov: 323 raise unless model.db.database_type == :sqlite 324 # :nocov: 325 end 326 end 327 end 328 end 329 end 330 ret 331 end
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.
# File lib/autoforme/models/sequel.rb 206 def paginate(type, request, ds, opts={}) 207 return ds.all if opts[:all_results] 208 limit = limit_for(type, request) 209 %r{\/(\d+)\z} =~ request.env['PATH_INFO'] 210 offset = (($1||1).to_i - 1) * limit 211 objs = ds.limit(limit+1, (offset if offset > 0)).all 212 next_page = false 213 if objs.length > limit 214 next_page = true 215 objs.pop 216 end 217 [next_page, objs] 218 end
The primary key value for the given object.
# File lib/autoforme/models/sequel.rb 130 def primary_key_value(obj) 131 obj.pk 132 end
Save the object, returning the object if successful, or nil if not.
# File lib/autoforme/models/sequel.rb 124 def save(obj) 125 obj.raise_on_save_failure = false 126 obj.save 127 end
Returning array of matching objects for the current search page using the given parameters.
# File lib/autoforme/models/sequel.rb 169 def search_results(type, request, opts={}) 170 params = request.params 171 ds = apply_associated_eager(:search, request, all_dataset_for(type, request)) 172 columns_for(:search_form, request).each do |c| 173 if (v = params[c.to_s]) && !(v = v.to_s).empty? 174 if association?(c) 175 ref = model.association_reflection(c) 176 ads = ref.associated_dataset 177 if model_class = associated_model_class(c) 178 ads = model_class.apply_filter(:association, request, ads) 179 end 180 primary_key = S.qualify(ref.associated_class.table_name, ref.primary_key) 181 ds = ds.where(S.qualify(model.table_name, ref[:key])=>ads.where(primary_key=>v).select(primary_key)) 182 elsif column_type(c) == :string 183 ds = ds.where(S.ilike(S.qualify(model.table_name, c), "%#{ds.escape_like(v)}%")) 184 else 185 begin 186 typecasted_value = model.db.typecast_value(column_type(c), v) 187 rescue S::InvalidValue 188 ds = ds.where(false) 189 break 190 else 191 ds = ds.where(S.qualify(model.table_name, c)=>typecasted_value) 192 end 193 end 194 end 195 end 196 paginate(type, request, ds, opts) 197 end
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.
# File lib/autoforme/models/sequel.rb 159 def session_value(column) 160 filter do |ds, type, req| 161 ds.where(S.qualify(model.table_name, column)=>req.session[column]) 162 end 163 before_create do |obj, req| 164 obj.send("#{column}=", req.session[column]) 165 end 166 end
Set the fields for the given action type to the object based on the request params.
# File lib/autoforme/models/sequel.rb 35 def set_fields(obj, type, request, params) 36 columns_for(type, request).each do |col| 37 column = col 38 39 if association?(col) 40 ref = model.association_reflection(col) 41 ds = ref.associated_dataset 42 if model_class = associated_model_class(col) 43 ds = model_class.apply_filter(:association, request, ds) 44 end 45 46 v = params[ref[:key].to_s] 47 v = nil if v.to_s.strip == '' 48 if v 49 v = ds.first!(S.qualify(ds.model.table_name, ref.primary_key)=>v) 50 end 51 else 52 v = params[col.to_s] 53 end 54 55 obj.send("#{column}=", v) 56 end 57 end
All objects in the associated table that are not currently associated to the given object.
# File lib/autoforme/models/sequel.rb 343 def unassociated_mtm_objects(request, assoc, obj) 344 ref = model.association_reflection(assoc) 345 assoc_class = associated_model_class(assoc) 346 lambda do |ds| 347 subquery = model.db.from(ref[:join_table]). 348 select(ref.qualified_right_key). 349 where(ref.qualified_left_key=>obj.pk) 350 ds = ds.exclude(S.qualify(ref.associated_class.table_name, ref.associated_class.primary_key)=>subquery) 351 ds = assoc_class.apply_dataset_options(:association, request, ds) if assoc_class 352 ds 353 end 354 end
Retrieve underlying model instance with matching primary key
# File lib/autoforme/models/sequel.rb 135 def with_pk(type, request, pk) 136 dataset_for(type, request).with_pk!(pk) 137 end