Class: CmseditController

Inherits:
DcApplicationController show all
Defined in:
app/controllers/cmsedit_controller.rb

Overview

This is main controller for processing actions by DRG forms. It provides CRUD actions for editing MongoDB documents. DRG CMS does not require controller to be made for every document model but centers all actions into single controller. Logic required to control data entry is provided within DRG forms which are loaded dynamically for every action.

Most of data entry controls must therefore be done in document models definitions. And there are controls that cannot be done in document models. Like controls which include url parameters or accessing session variables. This is hard to be done in model therefore cmsedit_controls had to be invented. cmsedit_controls are modules with methods that are injected into cmsedit_controller and act in runtime like they are part of cmsedit_controller.

Since Ruby and Rails provide some automagic loading of modules DRG CMS controls must be saved into app/controllers/drgcms_controls folder. Every model can have its own controls file. dc_page model’s controls live in dc_page_controls.rb file. If model has embedded document its control’s would be found in model_embedded_controls.rb. By convention module names are declared in camel case, so our dc_page_controls.rb declares DrgcmsControls::DcPageControls module.

Controls (among other) may contain 6 fixed callback methods. These methods are:

  • dc_new_record

  • dc_before_edit

  • dc_before_save

  • dc_after_save

  • dc_before_delete

  • dc_after_delete

Methods dc_before_edit, before_save or before_delete may also effect flow of the application. If method return false (not nil but FalseClass) normal flow of the program is interrupted and last operation is canceled.

Second control methods that can be declared in DRG CMS controls are filters for viewing and sorting documents. It is often required that dynamic filters are applied to result_set documents.

result_set:
  filter: current_users_documents

Example implemented controls method:

def current_users_documents
  if dc_user_can(DcPermission::CAN_READ)
    dc_page.where(created_by: session[:user_id])
  else
    flash[:error] = 'You can not perform this operation!'
    nil
  end
end

If filter method returns false user will be presented with flash error.

Instance Method Summary collapse

Methods inherited from DcApplicationController

#dc_dump, #dc_edit_mode?, #dc_find_form_file, #dc_get_site, dc_get_site_, #dc_log_visit, #dc_render_404, #dc_user_has_role

Instance Method Details

#check_filter_optionsObject

Will check and set current filter options for result set. Subroutine of index method.



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'app/controllers/cmsedit_controller.rb', line 142

def check_filter_options() #:nodoc:
  table_name = @tables.first[1]
  session[table_name] ||= {}
# process page
  session[table_name][:page] = params[:page] if params[:page]
# new filter is applied
  if params[:filter]
    session[table_name][:filter] =
    if params[:filter] == 'off' # clear all values
      nil
    else
      [ params[:filter_field], params[:filter_oper], params[:record][params[:filter_field]] ].join("\t")
    end
    session[table_name][:page] = 1
    params[:filter] = nil # must be. Otherwise kaminari includes parameter on paging and everything goes wrong
  end
# if data model has field dc_site_id ensure that only documents which belong 
# to the site are selected.
  model = @tables.first[0]
  site_id = dc_get_site._id if dc_get_site
# dont't filter site if no dc_site_id field or user is ADMIN
  site_id = nil if !model.method_defined?('dc_site_id') or dc_user_can(DcPermission::CAN_ADMIN)
  if session[table_name][:filter]
    field, oper, value = session[table_name][:filter].split( "\t")
    field = '_id' if field == 'id' # must be
    value = /#{value}/i if oper == 'like' # do regex if operation is like
# when field type is ObjectId transform value    
    if model.fields[field] and model.fields[field].type == BSON::ObjectId
      value = BSON::ObjectId.from_string(value) rescue nil
      flash[:error] = t('drgcms.not_id') if value.nil?
    end
    @records =  if site_id
      model.where(dc_site_id: site_id, field => value)
    else
      model.where(field => value)
    end
  else
    @records = if site_id
      model.where(dc_site_id: site_id)
    else
      model
    end
  end
# pagination if required
  per_page = (@form['result_set']['per_page'] || 30).to_i
  if per_page > 0
    @records = @records.page(session[table_name][:page]).per(per_page)
  end
end

#check_sort_optionsObject

Will check and set sorting options for current result set. Subroutine of index method.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'app/controllers/cmsedit_controller.rb', line 87

def check_sort_options() #:nodoc:
  table_name = @tables.first[1]
  old_sort = session[table_name][:sort].to_s
  sort, direction = old_sort.split(' ')
# sort is requested
  if params['sort']
    # reverse sort if same selected
    if params['sort'] == sort
      direction = (direction == '1') ? '-1' : '1'
    end
    direction ||= 1
    sort = params[:sort]
    session[table_name][:sort] = "#{params['sort']} #{direction}"
    session[table_name][:page] = 1
  end
  @records.sort( sort => direction.to_i ) if session[table_name][:sort]
  params['sort'] = nil # otherwise there is problem with other links
end

#createObject

Create (or duplicate) action. Action is also used for turning filter on.



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'app/controllers/cmsedit_controller.rb', line 361

def create
# abusing create for turning filter on
  return index if params[:filter].to_s == 'on'
# not authorized
  unless dc_user_can(DcPermission::CAN_CREATE)
    flash[:error] = t('drgcms.not_authorized')
    return index
  end
#
  if params['id'].nil? # create record
    create_new_empty_record
    params[:return_to] = 'index' if params[:commit] == t('drgcms.save&back') # save & back
    if save_data
      @parms['action'] = 'update'
      @parms['id'] = @record.id     # must be set, for proper update link
      flash[:info] = t('drgcms.doc_saved')
      return process_return_to(params[:return_to]) if params[:return_to]
      render action: :edit
    else
      render action: :new
    end
  else # duplicate record
    find_record
    params['dup_fields'] += ',' if params['dup_fields'] # for easier field matching
    new_doc = duplicate_document(@record)
    create_new_empty_record(new_doc)
    update_standards()
    @record.save!

    index
  end
end

#destroyObject

Destroy action. Used also for enabling and disabling record.



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'app/controllers/cmsedit_controller.rb', line 440

def destroy
  find_record
# Which permission is required to delete 
  permission = if params['operation'].nil?
    if @record.respond_to?('created_by') # needs can_delete_all if created_by is present and not owner
      (@record.created_by == session[:user_id]) ? DcPermission::CAN_DELETE : DcPermission::CAN_DELETE_ALL
    else
      DcPermission::CAN_DELETE    # by default
    end
  else # enable or disable record
    if @record.respond_to?('created_by')
      (@record.created_by == session[:user_id]) ? DcPermission::CAN_EDIT : DcPermission::CAN_EDIT_ALL
    else
      DcPermission::CAN_EDIT      # by default
    end
  end
  ok2delete = dc_user_can(permission)
#
  case
# not authorized    
  when !ok2delete then
    flash[:error] = t('drgcms.not_authorized')
    return index
  when params['operation'].nil? then
# Process before delete callback
    if (m = callback_method('before_delete') )
      ret = call_callback_method(m)
# Don't do anything if return is false
      return index if ret.class == FalseClass
    end
# document deleted
    if @record.destroy
      save_journal(:delete)
      flash[:info] = t('drgcms.record_deleted')
# Process after delete callback
      if (m = callback_method('after_delete') ) then call_callback_method(m) end
# Process return_to link
      return process_return_to(params[:return_to]) if params[:return_to]
    else
      flash[:error] = dc_error_messages_for(@record)
    end
    return index
# deaktivate document    
  when params['operation'] == 'disable' then
    if @record.respond_to?('active')
      @record.active = false
      save_journal(:update, @record.changes)
      update_standards()
      @record.save
      flash[:info] = t('drgcms.doc_disabled')
    end
# reaktivate document
  when params['operation'] == 'enable' then
    if @record.respond_to?('active')
      @record.active = true
      update_standards()
      save_journal(:update, @record.changes)
      @record.save
      flash[:info] = t('drgcms.doc_enabled')
    end
  end
#
  @parms['action'] = 'update'
  render action: :edit
end

#duplicate_document(source) ⇒ Object

Will create duplicate document of source document. This method is used for duplicating document and is called from create action.



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'app/controllers/cmsedit_controller.rb', line 338

def duplicate_document(source)
  dest = {}
  source.attribute_names.each do |attr_name|
    next if attr_name == '_id' # don't duplicate _id
# if duplicate string must be added. Useful for unique attributes
    add_duplicate = params['dup_fields'].to_s.match(attr_name + ',')
    dest[attr_name] = source[attr_name]
    dest[attr_name] << ' dup' if add_duplicate
  end
#  
  source.embedded_relations.keys.each do |embedded_name|
    next if source[embedded_name].nil? # it happens
    dest[embedded_name] = []
    source[embedded_name].each do |ar|
      dest[embedded_name] << duplicate_embedded(ar)
    end
  end
  dest
end

#duplicate_embedded(source) ⇒ Object

Duplicate embedded document. Since embedded documents are returned differently then top level document. Subroutine of duplicate_socument.



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'app/controllers/cmsedit_controller.rb', line 314

def duplicate_embedded(source) #:nodoc:
# TODO Works for two embedded levels. Dies with third and more levels.
  dest = {}
  source.each do |attr_name, value|
    next if attr_name == '_id' # don't duplicate _id
    if value.class == Array
      dest[attr_name] = []
      value.each do |ar|
        dest[attr_name] << duplicate_embedded(ar)
      end
    else      
# if duplicate string must be added. Useful for unique attributes
      add_duplicate = params['dup_fields'].to_s.match(attr_name + ',')
      dest[attr_name] = value
      dest[attr_name] << ' dup' if add_duplicate
    end
  end
  dest
end

#editObject

Edit action.



397
398
399
400
401
402
403
404
405
# File 'app/controllers/cmsedit_controller.rb', line 397

def edit
  find_record
  if (m = callback_method('before_edit') )
    ret = call_callback_method(m)
# Don't do anything if return is false
    return index if ret.class == FalseClass
  end  
  @parms['action'] = 'update'
end

#filterObject

Filter action.



240
241
242
# File 'app/controllers/cmsedit_controller.rb', line 240

def filter
  index
end

#indexObject

Index action.



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'app/controllers/cmsedit_controller.rb', line 195

def index
# If result_set is not defined on form, then it will fail. :return_to should know where to go
  if @form['result_set'].nil?
    return process_return_to(params[:return_to] || 'reload') 
  end
# result set is defined by filter method in control object
  if @form['result_set']['filter']
    if respond_to?(@form['result_set']['filter'])
      @records = send @form['result_set']['filter']
# something iz wrong. flash[] should have explanation.
      if @records.class == FalseClass
        @records = []
        return render(action:  :index)
      end
# pagination      
      per_page = (@form['result_set']['per_page'] || 30).to_i
      @records = @records.page(params[:page]).per(per_page) if per_page > 0
    else
      p "Error: result_set:filter: #{@form['result_set']['filter']} not found in controls!"
    end
  else
    if @tables.size == 1 # for now enable only filtering of main tables
      check_filter_options()
      check_sort_options()
    else
      rec = @tables.first[0].find(@ids.first)          # top most document.id
      1.upto(@tables.size - 2) { |i| rec = rec.send(@tables[i][1].pluralize).find(@ids[i]) }  # find embedded childrens by ids
#      p rec,@tables, @tables.last[1].pluralize
      @records = rec.send(@tables.last[1].pluralize)   # current embedded set
# sort by order if order field is present in model
      if @tables.last[1].classify.constantize.respond_to?(:order)
        @records = @records.order_by('order asc')
      end
    end
  end
#
  respond_to do |format|
    format.html { render action:  :index }
    format.js   { render partial: :result }
  end
end

#loginObject

Login action. Used to login direct to CMS. It is mostly used when first time creating site and when something goes so wrong, that traditional login procedure is not available.

Login can be called directly with url site.com/cmsedit/login



259
260
261
262
# File 'app/controllers/cmsedit_controller.rb', line 259

def 
  session[:edit_mode] = 0 unless params[:ok]
  render action: 'login', layout: 'cms'
end

#logoutObject

Logout action. Used to logout direct from CMS.

Logout can be called directly with url site.com/cmsedit/logout



269
270
271
272
273
# File 'app/controllers/cmsedit_controller.rb', line 269

def logout 
  session[:edit_mode] = 0
  session[:user_id]   = nil
  render action: 'login', layout: 'cms'
end

#newObject

New action.



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'app/controllers/cmsedit_controller.rb', line 278

def new
  if (m = callback_method('before_new') )
    ret = call_callback_method(m)
# Don't do anything if return is false
    return index if ret.class == FalseClass
  end  
# clear flash messages.
  flash[:error] = flash[:warning] = flash[:info] = nil 
  create_new_empty_record
  table = @tables.last[1] + '.'
# initial values set on page
  if cookies[:record] and cookies[:record].size > 0
    Marshal.load(cookies[:record]).each do |k,v|
      k = k.to_s
      if k.match(table)
        field = k.split('.').last
        @record.send("#{field}=", v) if @record.respond_to?(field)
      end
    end
  end
# initial values set in url
  params.each do |k,v|
    if k.match(table)
      field = k.split('.').last
      @record.send("#{field}=", v) if @record.respond_to?(field)
    end
  end
# This is how we set default values for new record
  dc_new_record() if respond_to?('dc_new_record') 
  @parms['action'] = 'create'
end

#showObject

Show displays record in readonly mode.



247
248
249
250
# File 'app/controllers/cmsedit_controller.rb', line 247

def show
  find_record
  render action: 'edit', layout: 'cms'
end

#updateObject

Update action.



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'app/controllers/cmsedit_controller.rb', line 410

def update
  find_record
# check if record was not updated in mean time
  if @record.respond_to?(:updated_at)
    if params[:last_updated_at].to_i != @record.updated_at.to_i
      flash[:error] = t('drgcms.updated_by_other')
      return render(action: :edit)
    end
  end
#   
  if dc_user_can(DcPermission::CAN_EDIT_ALL) or
    ( @record.respond_to?('created_by') and
      @record.created_by == session[:user_id] and
      dc_user_can(DcPermission::CAN_EDIT) )
#
    if save_data
      params[:return_to] = 'index' if params[:commit] == t('drgcms.save&back') # save & back
      @parms['action'] = 'update'
    end
# Process return_to link
    return process_return_to(params[:return_to]) if params[:return_to] 
  else
    flash[:error] = t('drgcms.not_authorized')
  end
  render action: :edit
end

#user_filter_options(model) ⇒ Object

Set aditional filter options when filter is defined by filter method in control object.



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'app/controllers/cmsedit_controller.rb', line 109

def user_filter_options(model) #:nodoc:
# no filter is active or set off
  if params[:filter] == 'off' or (params[:filter].nil? and session[:tmp_filter].nil?)
    session[:tmp_filter] = nil
    return model
  end
# 
  table, field, oper, value = if params[:filter] == 'on' 
    [model.to_s, params[:filter_field], params[:filter_oper], params[:record][params[:filter_field]] ]
  else
    session[:tmp_filter].split("\t")
  end
# filter set previously on other collection
  if table != model.to_s
    session[:tmp_filter] = nil
    return model 
  end
#
  field = '_id' if field == 'id' # must be
  value = /#{value}/ if oper == 'like' # do regex if operation is like
# when field type is ObjectId transform value    
  if model.fields[field] and model.fields[field].type == BSON::ObjectId
    value = BSON::ObjectId.from_string(value) rescue nil
    flash[:error] = t('drgcms.not_id') if value.nil?
  end
# save filter to session  
  session[:tmp_filter] = [ table, field, oper, value ].join("\t")
  model.where(field => value)
end