Class: AutoForme::Models::Sequel
- Inherits:
-
AutoForme::Model
- Object
- AutoForme::Model
- AutoForme::Models::Sequel
- Defined in:
- lib/autoforme/models/sequel.rb
Overview
Sequel specific model class for AutoForme
Constant Summary collapse
- S =
Short reference to top level Sequel module, for easily calling methods
::Sequel
- SUPPORTED_ASSOCIATION_TYPES =
What association types to recognize. Other association types are ignored.
[:many_to_one, :one_to_one, :one_to_many, :many_to_many]
Constants inherited from AutoForme::Model
AutoForme::Model::AUTOCOMPLETE_TYPES, AutoForme::Model::DEFAULT_LIMIT, AutoForme::Model::DEFAULT_SUPPORTED_ACTIONS, AutoForme::Model::DEFAULT_TABLE_CLASS, AutoForme::Model::VALID_CONSTANT_NAME_REGEXP
Instance Attribute Summary collapse
-
#params_name ⇒ Object
readonly
The namespace for form parameter names for this model, needs to match the ones automatically used by Forme.
Attributes inherited from AutoForme::Model
Instance Method Summary collapse
-
#all_rows_for(type, request) ⇒ Object
Retrieve all matching rows for this model.
-
#apply_associated_eager(type, request, ds) ⇒ Object
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.
-
#apply_dataset_options(type, request, ds) ⇒ Object
Apply the model’s filter, eager, and order to the given dataset.
-
#apply_filter(type, request, ds) ⇒ Object
Apply the model’s filter to the given dataset.
-
#associated_class(assoc) ⇒ Object
The associated class for the given association.
-
#associated_mtm_objects(request, assoc, obj) ⇒ Object
The currently associated many to many objects for the association.
-
#associated_new_column_values(obj, assoc) ⇒ Object
An array of pairs mapping foreign keys in associated class to primary key value of current object.
-
#association?(column) ⇒ Boolean
Whether the column represents an association.
-
#association_autocomplete?(assoc, request) ⇒ Boolean
Whether to autocomplete for the given association.
-
#association_key(assoc) ⇒ Object
The foreign key column for the given many to one association.
-
#association_names(types = SUPPORTED_ASSOCIATION_TYPES) ⇒ Object
Array of association name strings for given association types.
-
#association_type(assoc) ⇒ Object
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.
-
#autocomplete(opts = {}) ⇒ Object
Return array of autocompletion strings for the request.
-
#base_class ⇒ Object
The base class for the underlying model, ::Sequel::Model.
-
#browse(type, request, opts = {}) ⇒ Object
Return array of matching objects for the current page.
-
#column_type(column) ⇒ Object
The schema type for the column.
-
#default_columns ⇒ Object
Return the default columns for this model.
-
#editable_mtm_association_names ⇒ Object
Array of many to many association name strings for editable many to many associations.
-
#form_param_name(assoc) ⇒ Object
The name of the form param for the given association.
-
#initialize ⇒ Sequel
constructor
Make sure the forme plugin is loaded into the model.
-
#mtm_association_names ⇒ Object
Array of many to many association name strings.
-
#mtm_update(request, assoc, obj, add, remove) ⇒ Object
Update the many to many association.
-
#paginate(type, request, ds, opts = {}) ⇒ Object
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.
-
#primary_key_value(obj) ⇒ Object
The primary key value for the given object.
-
#save(obj) ⇒ Object
Save the object, returning the object if successful, or nil if not.
-
#search_results(type, request, opts = {}) ⇒ Object
Returning array of matching objects for the current search page using the given parameters.
-
#session_value(column) ⇒ Object
Add a filter restricting access to only rows where the column name matching the session value.
-
#set_fields(obj, type, request, params) ⇒ Object
Set the fields for the given action type to the object based on the request params.
-
#unassociated_mtm_objects(request, assoc, obj) ⇒ Object
All objects in the associated table that are not currently associated to the given object.
-
#with_pk(type, request, pk) ⇒ Object
Retrieve underlying model instance with matching primary key.
Methods inherited from AutoForme::Model
#associated_model_class, #associated_object_display_name, #association_links_for, #autocomplete_options_for, #before_action_hook, #class_name, #column_options_for, #column_value, #columns_for, #default_object_display_name, #destroy, #display_name_for, #eager_for, #eager_graph_for, #edit_html_for, #filter_for, for, #form_attributes_for, #form_options_for, #hook, #inline_mtm_assocs, #lazy_load_association_links?, #limit_for, #link, #model, #mtm_association_select_options, #new, #object_display_name, #order_for, #page_footer_for, #page_header_for, #redirect_for, #select_options, #show_html_for, #supported_action?, #supported_mtm_edit?, #supported_mtm_update?, #table_class_for
Methods included from OptsAttributes
Constructor Details
#initialize ⇒ Sequel
Make sure the forme plugin is loaded into the model.
18 19 20 21 22 |
# File 'lib/autoforme/models/sequel.rb', line 18 def initialize(*) super model.plugin :forme @params_name = model.new.forme_namespace end |
Instance Attribute Details
#params_name ⇒ Object (readonly)
The namespace for form parameter names for this model, needs to match the ones automatically used by Forme.
15 16 17 |
# File 'lib/autoforme/models/sequel.rb', line 15 def params_name @params_name end |
Instance Method Details
#all_rows_for(type, request) ⇒ Object
Retrieve all matching rows for this model.
140 141 142 |
# File 'lib/autoforme/models/sequel.rb', line 140 def all_rows_for(type, request) all_dataset_for(type, request).all end |
#apply_associated_eager(type, request, ds) ⇒ Object
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.
222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/autoforme/models/sequel.rb', line 222 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) ⇒ Object
Apply the model’s filter, eager, and order to the given dataset
250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/autoforme/models/sequel.rb', line 250 def (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) ⇒ Object
Apply the model’s filter to the given dataset
242 243 244 245 246 247 |
# File 'lib/autoforme/models/sequel.rb', line 242 def apply_filter(type, request, ds) if filter = filter_for ds = filter.call(ds, type, request) end ds end |
#associated_class(assoc) ⇒ Object
The associated class for the given association
70 71 72 |
# File 'lib/autoforme/models/sequel.rb', line 70 def associated_class(assoc) model.association_reflection(assoc).associated_class end |
#associated_mtm_objects(request, assoc, obj) ⇒ Object
The currently associated many to many objects for the association
334 335 336 337 338 339 340 |
# File 'lib/autoforme/models/sequel.rb', line 334 def associated_mtm_objects(request, assoc, obj) ds = obj.send("#{assoc}_dataset") if assoc_class = associated_model_class(assoc) ds = assoc_class.(:association, request, ds) end ds end |
#associated_new_column_values(obj, assoc) ⇒ Object
An array of pairs mapping foreign keys in associated class to primary key value of current object
96 97 98 99 |
# File 'lib/autoforme/models/sequel.rb', line 96 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) ⇒ Boolean
Whether the column represents an association.
60 61 62 63 64 65 66 67 |
# File 'lib/autoforme/models/sequel.rb', line 60 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) ⇒ Boolean
Whether to autocomplete for the given association.
265 266 267 |
# File 'lib/autoforme/models/sequel.rb', line 265 def association_autocomplete?(assoc, request) (c = associated_model_class(assoc)) && c.(:association, request) end |
#association_key(assoc) ⇒ Object
The foreign key column for the given many to one association.
90 91 92 |
# File 'lib/autoforme/models/sequel.rb', line 90 def association_key(assoc) model.association_reflection(assoc)[:key] end |
#association_names(types = SUPPORTED_ASSOCIATION_TYPES) ⇒ Object
Array of association name strings for given association types. If a block is given, only include associations where the block returns truthy.
116 117 118 119 120 121 |
# File 'lib/autoforme/models/sequel.rb', line 116 def association_names(types=SUPPORTED_ASSOCIATION_TYPES) model.all_association_reflections. select{|r| types.include?(r[:type]) && (!block_given? || yield(r))}. map{|r| r[:name]}. sort_by(&:to_s) end |
#association_type(assoc) ⇒ Object
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.
78 79 80 81 82 83 84 85 86 87 |
# File 'lib/autoforme/models/sequel.rb', line 78 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 = {}) ⇒ Object
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)
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/autoforme/models/sequel.rb', line 276 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 = (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 ⇒ Object
The base class for the underlying model, ::Sequel::Model.
25 26 27 |
# File 'lib/autoforme/models/sequel.rb', line 25 def base_class S::Model end |
#browse(type, request, opts = {}) ⇒ Object
Return array of matching objects for the current page.
200 201 202 |
# File 'lib/autoforme/models/sequel.rb', line 200 def browse(type, request, opts={}) paginate(type, request, apply_associated_eager(:browse, request, all_dataset_for(type, request)), opts) end |
#column_type(column) ⇒ Object
The schema type for the column
237 238 239 |
# File 'lib/autoforme/models/sequel.rb', line 237 def column_type(column) (sch = model.db_schema[column]) && sch[:type] end |
#default_columns ⇒ Object
Return the default columns for this model
145 146 147 148 149 150 151 152 153 154 |
# File 'lib/autoforme/models/sequel.rb', line 145 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! end |
#editable_mtm_association_names ⇒ Object
Array of many to many association name strings for editable many to many associations.
103 104 105 106 107 |
# File 'lib/autoforme/models/sequel.rb', line 103 def editable_mtm_association_names association_names([:many_to_many]) do |r| model.method_defined?(r.add_method) && model.method_defined?(r.remove_method) end end |
#form_param_name(assoc) ⇒ Object
The name of the form param for the given association.
30 31 32 |
# File 'lib/autoforme/models/sequel.rb', line 30 def form_param_name(assoc) "#{params_name}[#{association_key(assoc)}]" end |
#mtm_association_names ⇒ Object
Array of many to many association name strings.
110 111 112 |
# File 'lib/autoforme/models/sequel.rb', line 110 def mtm_association_names association_names([:many_to_many]) end |
#mtm_update(request, assoc, obj, add, remove) ⇒ Object
Update the many to many association. add and remove should be arrays of primary key values of associated objects to add to the association.
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/autoforme/models/sequel.rb', line 305 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) begin model.db.transaction(:savepoint=>true){obj.send(meth, ret)} rescue S::UniqueConstraintViolation # Already added, safe to ignore rescue S::ConstraintViolation # Old versions of sqlite3 and jdbc-sqlite3 can raise generic # ConstraintViolation instead of UniqueConstraintViolation # :nocov: raise unless model.db.database_type == :sqlite # :nocov: end end end end end ret end |
#paginate(type, request, ds, opts = {}) ⇒ Object
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.
206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/autoforme/models/sequel.rb', line 206 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 |
#primary_key_value(obj) ⇒ Object
The primary key value for the given object.
130 131 132 |
# File 'lib/autoforme/models/sequel.rb', line 130 def primary_key_value(obj) obj.pk end |
#save(obj) ⇒ Object
Save the object, returning the object if successful, or nil if not.
124 125 126 127 |
# File 'lib/autoforme/models/sequel.rb', line 124 def save(obj) obj.raise_on_save_failure = false obj.save end |
#search_results(type, request, opts = {}) ⇒ Object
Returning array of matching objects for the current search page using the given parameters.
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/autoforme/models/sequel.rb', line 169 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 = v.to_s).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)}%")) else begin typecasted_value = model.db.typecast_value(column_type(c), v) rescue S::InvalidValue ds = ds.where(false) break else ds = ds.where(S.qualify(model.table_name, c)=>typecasted_value) end end end end paginate(type, request, ds, opts) end |
#session_value(column) ⇒ Object
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.
159 160 161 162 163 164 165 166 |
# File 'lib/autoforme/models/sequel.rb', line 159 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) ⇒ Object
Set the fields for the given action type to the object based on the request params.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/autoforme/models/sequel.rb', line 35 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) ⇒ Object
All objects in the associated table that are not currently associated to the given object.
343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/autoforme/models/sequel.rb', line 343 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.(:association, request, ds) if assoc_class ds end end |
#with_pk(type, request, pk) ⇒ Object
Retrieve underlying model instance with matching primary key
135 136 137 |
# File 'lib/autoforme/models/sequel.rb', line 135 def with_pk(type, request, pk) dataset_for(type, request).with_pk!(pk) end |