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
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.
-
#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.
-
#params_name ⇒ Object
The namespace for form parameter names for this model, needs to match the ones automatically used by Forme.
-
#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.
14 15 16 17 |
# File 'lib/autoforme/models/sequel.rb', line 14 def initialize(*) super model.plugin :forme end |
Instance Method Details
#all_rows_for(type, request) ⇒ Object
Retrieve all matching rows for this model.
129 130 131 |
# File 'lib/autoforme/models/sequel.rb', line 129 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.
204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/autoforme/models/sequel.rb', line 204 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
232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/autoforme/models/sequel.rb', line 232 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
224 225 226 227 228 229 |
# File 'lib/autoforme/models/sequel.rb', line 224 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
65 66 67 |
# File 'lib/autoforme/models/sequel.rb', line 65 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
306 307 308 309 310 311 312 |
# File 'lib/autoforme/models/sequel.rb', line 306 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
91 92 93 94 |
# File 'lib/autoforme/models/sequel.rb', line 91 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.
55 56 57 58 59 60 61 62 |
# File 'lib/autoforme/models/sequel.rb', line 55 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.
247 248 249 |
# File 'lib/autoforme/models/sequel.rb', line 247 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.
85 86 87 |
# File 'lib/autoforme/models/sequel.rb', line 85 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
102 103 104 |
# File 'lib/autoforme/models/sequel.rb', line 102 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) ⇒ 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.
73 74 75 76 77 78 79 80 81 82 |
# File 'lib/autoforme/models/sequel.rb', line 73 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)
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/autoforme/models/sequel.rb', line 258 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.
20 21 22 |
# File 'lib/autoforme/models/sequel.rb', line 20 def base_class S::Model end |
#browse(type, request, opts = {}) ⇒ Object
Return array of matching objects for the current page.
182 183 184 |
# File 'lib/autoforme/models/sequel.rb', line 182 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
219 220 221 |
# File 'lib/autoforme/models/sequel.rb', line 219 def column_type(column) (sch = model.db_schema[column]) && sch[:type] end |
#default_columns ⇒ Object
Return the default columns for this model
134 135 136 137 138 139 140 141 142 143 |
# File 'lib/autoforme/models/sequel.rb', line 134 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) ⇒ Object
The name of the form param for the given association.
25 26 27 |
# File 'lib/autoforme/models/sequel.rb', line 25 def form_param_name(assoc) "#{model.send(:underscore, model.name)}[#{association_key(assoc)}]" end |
#mtm_association_names ⇒ Object
Array of many to many association name strings.
97 98 99 |
# File 'lib/autoforme/models/sequel.rb', line 97 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.
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/autoforme/models/sequel.rb', line 287 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 = {}) ⇒ 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.
188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/autoforme/models/sequel.rb', line 188 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 ⇒ Object
The namespace for form parameter names for this model, needs to match the ones automatically used by Forme.
119 120 121 |
# File 'lib/autoforme/models/sequel.rb', line 119 def params_name model.send(:underscore, model.name) end |
#primary_key_value(obj) ⇒ Object
The primary key value for the given object.
113 114 115 |
# File 'lib/autoforme/models/sequel.rb', line 113 def primary_key_value(obj) obj.pk end |
#save(obj) ⇒ Object
Save the object, returning the object if successful, or nil if not.
107 108 109 110 |
# File 'lib/autoforme/models/sequel.rb', line 107 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.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/autoforme/models/sequel.rb', line 158 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) ⇒ 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.
148 149 150 151 152 153 154 155 |
# File 'lib/autoforme/models/sequel.rb', line 148 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.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/autoforme/models/sequel.rb', line 30 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.
315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/autoforme/models/sequel.rb', line 315 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
124 125 126 |
# File 'lib/autoforme/models/sequel.rb', line 124 def with_pk(type, request, pk) dataset_for(type, request).with_pk!(pk) end |