# File lib/autoforme/models/sequel.rb 130 def primary_key_value(obj) 131 obj.pk 132 end
class AutoForme::Models::Sequel
Constants
- S
Short reference to top level
Sequel
module, for easily calling methods- SUPPORTED_ASSOCIATION_TYPES
What association types to recognize. Other association types are ignored.
Attributes
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.
AutoForme::Model::new
# 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 328 def associated_mtm_objects(request, assoc, obj) 329 ds = obj.send("#{assoc}_dataset") 330 if assoc_class = associated_model_class(assoc) 331 ds = assoc_class.apply_dataset_options(:association, request, ds) 332 end 333 ds 334 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
-
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)
# 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_by(&:to_s) 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 end 320 end 321 end 322 end 323 end 324 ret 325 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.
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 337 def unassociated_mtm_objects(request, assoc, obj) 338 ref = model.association_reflection(assoc) 339 assoc_class = associated_model_class(assoc) 340 lambda do |ds| 341 subquery = model.db.from(ref[:join_table]). 342 select(ref.qualified_right_key). 343 where(ref.qualified_left_key=>obj.pk) 344 ds = ds.exclude(S.qualify(ref.associated_class.table_name, ref.associated_class.primary_key)=>subquery) 345 ds = assoc_class.apply_dataset_options(:association, request, ds) if assoc_class 346 ds 347 end 348 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
Private Instance Methods
# File lib/autoforme/models/sequel.rb 360 def all_dataset_for(type, request) 361 apply_dataset_options(type, request, model.dataset) 362 end
# File lib/autoforme/models/sequel.rb 352 def dataset_for(type, request) 353 ds = model.dataset 354 if filter = filter_for 355 ds = filter.call(ds, type, request) 356 end 357 ds 358 end