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.
-
#new_search ⇒ Object
A completely empty search object, with no defaults.
-
#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.
134 135 136 |
# File 'lib/autoforme/models/sequel.rb', line 134 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.
209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/autoforme/models/sequel.rb', line 209 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
237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/autoforme/models/sequel.rb', line 237 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
229 230 231 232 233 234 |
# File 'lib/autoforme/models/sequel.rb', line 229 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
311 312 313 314 315 316 317 |
# File 'lib/autoforme/models/sequel.rb', line 311 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.
252 253 254 |
# File 'lib/autoforme/models/sequel.rb', line 252 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
107 108 109 |
# File 'lib/autoforme/models/sequel.rb', line 107 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.
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)
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/autoforme/models/sequel.rb', line 263 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{|ds, opts| ds.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.
187 188 189 |
# File 'lib/autoforme/models/sequel.rb', line 187 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
224 225 226 |
# File 'lib/autoforme/models/sequel.rb', line 224 def column_type(column) (sch = model.db_schema[column]) && sch[:type] end |
#default_columns ⇒ Object
Return the default columns for this model
139 140 141 142 143 144 145 146 147 148 |
# File 'lib/autoforme/models/sequel.rb', line 139 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.
30 31 32 |
# File 'lib/autoforme/models/sequel.rb', line 30 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.
102 103 104 |
# File 'lib/autoforme/models/sequel.rb', line 102 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.
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/autoforme/models/sequel.rb', line 292 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 |
#new_search ⇒ Object
A completely empty search object, with no defaults.
25 26 27 |
# File 'lib/autoforme/models/sequel.rb', line 25 def new_search model.call({}) 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.
193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/autoforme/models/sequel.rb', line 193 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.
124 125 126 |
# File 'lib/autoforme/models/sequel.rb', line 124 def params_name model.send(:underscore, model.name) end |
#primary_key_value(obj) ⇒ Object
The primary key value for the given object.
118 119 120 |
# File 'lib/autoforme/models/sequel.rb', line 118 def primary_key_value(obj) obj.pk end |
#save(obj) ⇒ Object
Save the object, returning the object if successful, or nil if not.
112 113 114 115 |
# File 'lib/autoforme/models/sequel.rb', line 112 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.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/autoforme/models/sequel.rb', line 163 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.
153 154 155 156 157 158 159 160 |
# File 'lib/autoforme/models/sequel.rb', line 153 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.
320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/autoforme/models/sequel.rb', line 320 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, model.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
129 130 131 |
# File 'lib/autoforme/models/sequel.rb', line 129 def with_pk(type, request, pk) dataset_for(type, request).with_pk!(pk) end |