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
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) ⇒ 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) ⇒ 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) ⇒ 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, #filter_for, for, #form_attributes_for, #form_options_for, #hook, #inline_mtm_assocs, #lazy_load_association_links?, #limit_for, #link, #mtm_association_select_options, #new, #object_display_name, #order_for, #page_footer_for, #page_header_for, #redirect_for, #select_options, #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.
12 13 14 15 |
# File 'lib/autoforme/models/sequel.rb', line 12 def initialize(*) super @model.plugin :forme end |
Instance Method Details
#all_rows_for(type, request) ⇒ Object
Retrieve all matching rows for this model.
132 133 134 |
# File 'lib/autoforme/models/sequel.rb', line 132 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.
205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/autoforme/models/sequel.rb', line 205 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
233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/autoforme/models/sequel.rb', line 233 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
225 226 227 228 229 230 |
# File 'lib/autoforme/models/sequel.rb', line 225 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
68 69 70 |
# File 'lib/autoforme/models/sequel.rb', line 68 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
307 308 309 310 311 312 313 |
# File 'lib/autoforme/models/sequel.rb', line 307 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
94 95 96 97 |
# File 'lib/autoforme/models/sequel.rb', line 94 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.
58 59 60 61 62 63 64 65 |
# File 'lib/autoforme/models/sequel.rb', line 58 def association?(column) case column when String model.associations.map{|x| x.to_s}.include?(column) else model.association_reflection(column) end end |
#association_autocomplete?(assoc, request) ⇒ Boolean
Whether to autocomplete for the given association.
248 249 250 |
# File 'lib/autoforme/models/sequel.rb', line 248 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.
88 89 90 |
# File 'lib/autoforme/models/sequel.rb', line 88 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
105 106 107 |
# File 'lib/autoforme/models/sequel.rb', line 105 def association_names(types=SUPPORTED_ASSOCIATION_TYPES) model.all_association_reflections.select{|r| types.include?(r[:type])}.map{|r| r[:name]}.sort_by{|n| n.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.
76 77 78 79 80 81 82 83 84 85 |
# File 'lib/autoforme/models/sequel.rb', line 76 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)
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 284 |
# File 'lib/autoforme/models/sequel.rb', line 259 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.
18 19 20 |
# File 'lib/autoforme/models/sequel.rb', line 18 def base_class S::Model end |
#browse(type, request) ⇒ Object
Return array of matching objects for the current page.
185 186 187 |
# File 'lib/autoforme/models/sequel.rb', line 185 def browse(type, request) paginate(type, request, apply_associated_eager(:browse, request, all_dataset_for(type, request))) end |
#column_type(column) ⇒ Object
The schema type for the column
220 221 222 |
# File 'lib/autoforme/models/sequel.rb', line 220 def column_type(column) (sch = model.db_schema[column]) && sch[:type] end |
#default_columns ⇒ Object
Return the default columns for this model
137 138 139 140 141 142 143 144 145 146 |
# File 'lib/autoforme/models/sequel.rb', line 137 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{|s| s.to_s} end |
#form_param_name(assoc) ⇒ Object
The name of the form param for the given association.
28 29 30 |
# File 'lib/autoforme/models/sequel.rb', line 28 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.
100 101 102 |
# File 'lib/autoforme/models/sequel.rb', line 100 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.
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/autoforme/models/sequel.rb', line 288 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.
23 24 25 |
# File 'lib/autoforme/models/sequel.rb', line 23 def new_search @model.call({}) end |
#paginate(type, request, ds) ⇒ 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.
191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/autoforme/models/sequel.rb', line 191 def paginate(type, request, ds) limit = limit_for(type, request) offset = ((request.id.to_i||1)-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.
122 123 124 |
# File 'lib/autoforme/models/sequel.rb', line 122 def params_name @model.send(:underscore, @model.name) end |
#primary_key_value(obj) ⇒ Object
The primary key value for the given object.
116 117 118 |
# File 'lib/autoforme/models/sequel.rb', line 116 def primary_key_value(obj) obj.pk end |
#save(obj) ⇒ Object
Save the object, returning the object if successful, or nil if not.
110 111 112 113 |
# File 'lib/autoforme/models/sequel.rb', line 110 def save(obj) obj.raise_on_save_failure = false obj.save end |
#search_results(type, request) ⇒ Object
Returning array of matching objects for the current search page using the given parameters.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/autoforme/models/sequel.rb', line 161 def search_results(type, request) 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) 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.
151 152 153 154 155 156 157 158 |
# File 'lib/autoforme/models/sequel.rb', line 151 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.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/autoforme/models/sequel.rb', line 33 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.
316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/autoforme/models/sequel.rb', line 316 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
127 128 129 |
# File 'lib/autoforme/models/sequel.rb', line 127 def with_pk(type, request, pk) dataset_for(type, request).with_pk!(pk) end |