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 146 def all_rows_for(type, request) 147 all_dataset_for(type, request).all 148 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 228 def apply_associated_eager(type, request, ds) 229 columns_for(type, request).each do |col| 230 if association?(col) 231 if model = associated_model_class(col) 232 eager = model.eager_for(:association, request) || model.eager_graph_for(:association, request) 233 ds = ds.eager(col=>eager) 234 else 235 ds = ds.eager(col) 236 end 237 end 238 end 239 ds 240 end
Apply the model’s filter, eager, and order to the given dataset
# File lib/autoforme/models/sequel.rb 256 def apply_dataset_options(type, request, ds) 257 ds = apply_filter(type, request, ds) 258 if order = order_for(type, request) 259 ds = ds.order(*order) 260 end 261 if eager = eager_for(type, request) 262 ds = ds.eager(eager) 263 end 264 if eager_graph = eager_graph_for(type, request) 265 ds = ds.eager_graph(eager_graph) 266 end 267 ds 268 end
Apply the model’s filter to the given dataset
# File lib/autoforme/models/sequel.rb 248 def apply_filter(type, request, ds) 249 if filter = filter_for 250 ds = filter.call(ds, type, request) 251 end 252 ds 253 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 340 def associated_mtm_objects(request, assoc, obj) 341 ds = obj.send("#{assoc}_dataset") 342 if assoc_class = associated_model_class(assoc) 343 ds = assoc_class.apply_dataset_options(:association, request, ds) 344 end 345 ds 346 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 271 def association_autocomplete?(assoc, request) 272 (c = associated_model_class(assoc)) && c.autocomplete_options_for(:association, request) 273 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 282 def autocomplete(opts={}) 283 type, request, assoc, query, exclude = opts.values_at(:type, :request, :association, :query, :exclude) 284 if assoc 285 if exclude && association_type(assoc) == :edit 286 ref = model.association_reflection(assoc) 287 block = lambda do |ds| 288 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])) 289 end 290 end 291 return associated_model_class(assoc).autocomplete(opts.merge(:type=>:association, :association=>nil), &block) 292 end 293 opts = autocomplete_options_for(type, request) 294 callback_opts = {:type=>type, :request=>request, :query=>query} 295 ds = all_dataset_for(type, request) 296 ds = opts[:callback].call(ds, callback_opts) if opts[:callback] 297 display = opts[:display] || S.qualify(model.table_name, :name) 298 display = display.call(callback_opts) if display.respond_to?(:call) 299 limit = opts[:limit] || 10 300 limit = limit.call(callback_opts) if limit.respond_to?(:call) 301 opts[:filter] ||= lambda{|ds1, _| ds1.where(S.ilike(display, "%#{ds.escape_like(query)}%"))} 302 ds = opts[:filter].call(ds, callback_opts) 303 ds = ds.select(S.join([S.qualify(model.table_name, model.primary_key), display], ' - ').as(:v)). 304 limit(limit) 305 ds = yield ds if block_given? 306 ds.map(:v) 307 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 206 def browse(type, request, opts={}) 207 paginate(type, request, apply_associated_eager(:browse, request, all_dataset_for(type, request)), opts) 208 end
The schema type for the column
# File lib/autoforme/models/sequel.rb 243 def column_type(column) 244 (sch = model.db_schema[column]) && sch[:type] 245 end
Return the default columns for this model
# File lib/autoforme/models/sequel.rb 151 def default_columns 152 columns = model.columns - Array(model.primary_key) 153 model.all_association_reflections.each do |reflection| 154 next unless reflection[:type] == :many_to_one 155 if i = columns.index(reflection[:key]) 156 columns[i] = reflection[:name] 157 end 158 end 159 columns.sort! 160 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 311 def mtm_update(request, assoc, obj, add, remove) 312 ref = model.association_reflection(assoc) 313 assoc_class = associated_model_class(assoc) 314 ret = nil 315 model.db.transaction do 316 [[add, ref.add_method], [remove, ref.remove_method]].each do |ids, meth| 317 if ids 318 ids.each do |id| 319 next if id.to_s.empty? 320 ret = assoc_class ? assoc_class.with_pk(:association, request, id) : obj.send(:_apply_association_options, ref, ref.associated_class.dataset.clone).with_pk!(id) 321 begin 322 model.db.transaction(:savepoint=>true){obj.send(meth, ret)} 323 rescue S::UniqueConstraintViolation 324 # Already added, safe to ignore 325 rescue S::ConstraintViolation 326 # Old versions of sqlite3 and jdbc-sqlite3 can raise generic 327 # ConstraintViolation instead of UniqueConstraintViolation 328 # :nocov: 329 raise unless model.db.database_type == :sqlite 330 # :nocov: 331 end 332 end 333 end 334 end 335 end 336 ret 337 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 212 def paginate(type, request, ds, opts={}) 213 return ds.all if opts[:all_results] 214 limit = limit_for(type, request) 215 %r{\/(\d+)\z} =~ request.env['PATH_INFO'] 216 offset = (($1||1).to_i - 1) * limit 217 objs = ds.limit(limit+1, (offset if offset > 0)).all 218 next_page = false 219 if objs.length > limit 220 next_page = true 221 objs.pop 222 end 223 [next_page, objs] 224 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 175 def search_results(type, request, opts={}) 176 params = request.params 177 ds = apply_associated_eager(:search, request, all_dataset_for(type, request)) 178 columns_for(:search_form, request).each do |c| 179 if (v = params[c.to_s]) && !(v = v.to_s).empty? 180 if association?(c) 181 ref = model.association_reflection(c) 182 ads = ref.associated_dataset 183 if model_class = associated_model_class(c) 184 ads = model_class.apply_filter(:association, request, ads) 185 end 186 primary_key = S.qualify(ref.associated_class.table_name, ref.primary_key) 187 ds = ds.where(S.qualify(model.table_name, ref[:key])=>ads.where(primary_key=>v).select(primary_key)) 188 elsif column_type(c) == :string 189 ds = ds.where(S.ilike(S.qualify(model.table_name, c), "%#{ds.escape_like(v)}%")) 190 else 191 begin 192 typecasted_value = typecast_value(c, v) 193 rescue S::InvalidValue 194 ds = ds.where(false) 195 break 196 else 197 ds = ds.where(S.qualify(model.table_name, c)=>typecasted_value) 198 end 199 end 200 end 201 end 202 paginate(type, request, ds, opts) 203 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 165 def session_value(column) 166 filter do |ds, type, req| 167 ds.where(S.qualify(model.table_name, column)=>req.session[column]) 168 end 169 before_create do |obj, req| 170 obj.send("#{column}=", req.session[column]) 171 end 172 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 349 def unassociated_mtm_objects(request, assoc, obj) 350 ref = model.association_reflection(assoc) 351 assoc_class = associated_model_class(assoc) 352 lambda do |ds| 353 subquery = model.db.from(ref[:join_table]). 354 select(ref.qualified_right_key). 355 where(ref.qualified_left_key=>obj.pk) 356 ds = ds.exclude(S.qualify(ref.associated_class.table_name, ref.associated_class.primary_key)=>subquery) 357 ds = assoc_class.apply_dataset_options(:association, request, ds) if assoc_class 358 ds 359 end 360 end
Retrieve underlying model instance with matching primary key
# File lib/autoforme/models/sequel.rb 135 def with_pk(type, request, pk) 136 begin 137 pk = typecast_value(model.primary_key, pk) 138 rescue S::InvalidValue 139 raise S::NoMatchingRow 140 end 141 142 dataset_for(type, request).with_pk!(pk) 143 end