Class: AgileController

Inherits:
AgileApplicationController show all
Defined in:
app/controllers/agile_controller.rb

Overview

This is main controller for processing actions by AgileRails forms. It provides CRUD actions for editing database records. AgileRails does not require controller to be made for every table model but implements all actions in single controller. Logic required to control data entry is provided within AgileRails forms which are loaded dynamically for every action.

Data entry validations must therefore reside in document models definitions or can be implemented in forms. There are always validations that cannot be done in models. Like validations which include url parameters or accessing session variables. This is hard to be done in model therefore AgileRails controls had to be invented. AgileRails controls are modules with methods that are injected into agile controller and act in runtime like they are part of Agile controller.

Since Ruby and Rails provide some "automagic" loading of modules AgileRails controls must be saved into app/controls folder. Every model can have its own controls file. ar_page model's controls live in ar_page_controls.rb file. By convention module names are declared in camel case, so our ar_page_controls.rb declares ArPageControls module.

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

  • before_new
  • new_record
  • dup_record
  • before_edit
  • before_save
  • after_save
  • before_delete
  • after_delete

Methods before_new, 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 AgileRails controls are filters for viewing and sorting documents. It is often required that dynamic filters are applied to data_set documents.

data_set:
 filter: current_users_documents

Example implemented controls method:

def current_users_documents
 if agile_user_can(ArPermission::CAN_READ)
   ArPage.where(created_by: session[:user_id])
 else
   flash[:error] = 'User can not perform this operation!'
   false
 end
end

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

Instance Method Summary collapse

Methods inherited from AgileApplicationController

#agile_dump, #agile_edit_mode?, #agile_get_site, #agile_process_default_request, #agile_render_404, #agile_user_has_role?, #agile_visit_log, find_help_file, #set_page_title

Instance Method Details

#_filterObject

Filter action.



102
103
104
# File 'app/controllers/agile_controller.rb', line 102

def _filter
  index
end

#createObject

Create (or duplicate) action.



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'app/controllers/agile_controller.rb', line 214

def create
  # not authorized
  unless agile_user_can(ArPermission::CAN_CREATE)
    flash[:error] = t('agile.not_authorized')
    return index
  end

  # create document
  if params['id'].nil?
    # Prevent double form submit
    return index if double_form_submit?

    create_new_empty_record
    if save_data
      flash[:info] = t('agile.record_saved')
      params[:return_to] = 'index' if params[:commit] == t('agile.save&back') # save & back
      return process_return_to(params[:return_to]) if params[:return_to]

      @form_params['id'] = @record.id # must be set, for proper update link
      params[:id] = @record.id # must be set, for find_record
      edit
    else # error
      return process_return_to(params[:return_to]) if params[:return_to]

      render action: :new
    end
  else # duplicate record
    find_record
    new_record = duplicate_record(@record)
    create_new_empty_record(new_record)
    if (m = callback_method('dup_record')) then callback_method_call(m) end
    update_standards
    @record.save!
    index
  end
end

#destroyObject

Destroy action. Used also for enabling and disabling record.



300
301
302
303
304
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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
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
393
# File 'app/controllers/agile_controller.rb', line 300

def destroy
  find_record
  # check permission 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]) ? ArPermission::CAN_DELETE : ArPermission::CAN_DELETE_ALL
    else
      ArPermission::CAN_DELETE    # by default
    end
  else # enable or disable record
    if @record.respond_to?('created_by')
      (@record.created_by == session[:user_id]) ? ArPermission::CAN_EDIT : ArPermission::CAN_EDIT_ALL
    else
      ArPermission::CAN_EDIT      # by default
    end
  end
  ok2delete = agile_user_can(permission)

  case
  # not authorized
  when !ok2delete then
    flash[:error] = t('agile.not_authorized')
    return index

  # delete document
  when params['operation'].nil? then
    # before_delete callback
    if (m = callback_method('before_delete') )
      ret = callback_method_call(m)
      # don't do anything if return is false
      return index if ret.class == FalseClass
    end

    # take care of transaction
    begin
      transaction_begin()
      if @record.destroy
        save_journal(:delete)
        flash[:info] = t('agile.record_deleted')
        # after_delete callback
        if (m = callback_method('after_delete') )
          callback_method_call(m)
        elsif params['after-delete'].to_s.match('return_to')
          params[:return_to] = params['after-delete']
        end
        # Process return_to link
        if params[:return_to]
          transaction_end()
          return process_return_to(params[:return_to])
        end
      else
        flash[:error] = agile_error_messages_for(@record)
        transaction_abort('')
      end
    rescue Exception => e
      transaction_abort()
      transaction_end()
      logger.error(%(#{e.message}\n\n#{e.backtrace.join("\n")}))
      return if Rails.env.test? # or test will fail

      raise
    end
    # end transaction normaly
    transaction_end()
    return index
    
  # deactivate 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('agile.record_disabled')
    end
    
  # reactivate 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('agile.record_enabled')
    end

  #TODO reorder documents
  when params['operation'] == 'reorder' then

  end

  @form_params['action'] = 'update'
  render action: :edit
end

#duplicate_record(source) ⇒ Object

Will duplicate source document into new record. This method is used for duplicating record and is subroutine of create action.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'app/controllers/agile_controller.rb', line 196

def duplicate_record(source)
  duplicates = params['dup_fields'].split(',').map(&:strip)
  dest = {}
  source.attribute_names.each do |attribute_name|
    next if attribute_name == 'id' # don't duplicate _id

    dest[attribute_name] = source[attribute_name]
    # if duplicate, string dup is added. For unique fields
    dest[attribute_name] += ' dup' if duplicates.include?(attribute_name)
  end
  dest['created_at'] = Time.now if dest['created_at']
  dest['updated_at'] = Time.now if dest['updated_at']
  dest
end

#editObject

Edit action.



254
255
256
257
258
259
260
261
262
263
# File 'app/controllers/agile_controller.rb', line 254

def edit
  find_record
  if (m = callback_method('before_edit') )
    ret = callback_method_call(m)
    # don't do anything if return is false
    return index if ret.class == FalseClass
  end
  @form_params['action'] = 'update'
  render action: :edit
end

#indexObject

Index action



87
88
89
90
91
92
93
94
95
96
97
# File 'app/controllers/agile_controller.rb', line 87

def index
  @form['index']['data_set'] ||= {}
  redirected = (@form['table'] == 'ar_memory' ? process_in_memory : process_data_set)
  return if redirected

  callback_method_call(@form.dig('index', 'data_set', 'footer') || 'update_footer')
  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 common login procedure is not available.

Login can be called directly with url http://site.com/agile/login



130
131
132
133
134
135
# File 'app/controllers/agile_controller.rb', line 130

def 
  return set_development_site if params[:id] == 'test'

  session[:edit_mode] = 0 unless params[:ok]
  render action: 'login'
end

#logoutObject

Logout action. Used to logout direct from CMS.

Logout can be called directly with url http://site.com/agile/logout



142
143
144
145
146
147
# File 'app/controllers/agile_controller.rb', line 142

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

#newObject

New action.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'app/controllers/agile_controller.rb', line 171

def new
  flash[:error] = flash[:warning] = flash[:info] = nil
  # not authorized
  unless agile_user_can(ArPermission::CAN_CREATE)
    flash[:error] = t('agile.not_authorized')
    logger.error("*******  #{t('agile.not_authorized')} #{session[:user_name]} #{AgileHelper.table_param(params)}")
    return index
  end
  create_new_empty_record()

  if (m = callback_method('before_new') )
    ret = callback_method_call(m)
    return index if ret.class == FalseClass
  end
  load_initial_values()

  # new_record callback. Set default values for new record
  if (m = callback_method('new_record') ) then callback_method_call(m)  end
  @form_params['action'] = 'create'
end

#runObject

Run action



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'app/controllers/agile_controller.rb', line 398

def run
  # determine control file name and method
  control_name, method_name = params[:control].split('.')
  if method_name.nil?
    method_name  = control_name
    control_name = AgileHelper.table_param(params)
  end
  # extend with control methods
  extend_with_control_module(control_name)
  if respond_to?(method_name)
    # can it be called
    return return_run_error t('agile.not_authorized') unless can_process_run
    # call method
    respond_to do |format|
      format.json { send method_name }
      format.html { send method_name }
    end    
  else # Error message
    return_run_error "Method #{method_name} not defined in #{control_name}_control"
  end
end

#set_development_siteObject

Shortcut for setting currently selected site in development. Will search for ar_site document with site name 'development' and set alias_for to site url parameter.



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'app/controllers/agile_controller.rb', line 154

def set_development_site
  # only in development
  return  agile_render_404 unless Rails.env.development?

  alias_site = ArSite.find_by(name: params[:site])
  return agile_render_404 unless alias_site

  # update alias for  
  site = ArSite.find_by(name: 'development')
  site.alias_for = params[:site]
  site.save
  redirect_to '/'
end

#showObject

Show displays record in readonly mode.



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'app/controllers/agile_controller.rb', line 109

def show
  find_record
  # before_show callback
  if (m = callback_method('before_show') )
    ret = callback_method_call(m)
    if ret.class == FalseClass
      @form['readonly'] = nil # must be
      return index 
    end
  end  

  render action: 'edit'
end

#updateObject

Update action.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'app/controllers/agile_controller.rb', line 268

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('agile.updated_by_other')
      return render(action: :edit)
    end
  end

  if agile_user_can(ArPermission::CAN_EDIT_ALL) ||
    (@record.respond_to?('created_by') && @record.created_by == session[:user_id] && agile_user_can(ArPermission::CAN_EDIT))

    if save_data
      params[:return_to] = 'index' if params[:commit] == t('agile.save&back') # save & back
      @form_params['action'] = 'update'
      # Process return_to
      return process_return_to(params[:return_to]) if params[:return_to]
    else
      # do not forget before_edit callback
      if m = callback_method('before_edit') then callback_method_call(m) end
      return render action: :edit
    end
  else
    flash[:error] = t('agile.not_authorized')
  end
  edit
end