Class: EdgarController
- Inherits:
-
ApplicationController
- Object
- ApplicationController
- EdgarController
- Includes:
- Edgar::ControllerCommon, Edgar::PermissionMixin, Edgar::RescueMixin
- Defined in:
- app/controllers/edgar_controller.rb
Overview
Generic CRUD(Create/Read/Update/Delte) controller with Ajax.
EdgarController v.s. ApplicationController
When concreate controller (e.g. CustomersController) inherits EdgarController, it has the following features:
-
CRUD with default form on Ajax
-
form can be customized.
-
-
QBE (Query By Example) search form
-
popup-selection on 'belongs_to' column
-
sort on list
-
saving search-conditions and reuse it
If these are not necessary, just inherits ApplicationController.
Tasks when adding new model which is handled under EdgarController
For example, when want to add Post model:
-
generate edgar_scaffold:
./script/generate edgar_scaffold post name:string body:text published:boolean -
add controller entry to CONTROLLER_MENU (config/initializers/edgar.rb)
It will take about ~3 min.
For the detail of customization, please see:
http://sourceforge.net/apps/trac/jjedgar/wiki/customize
Architecture
see architecture (OpenOffice Presentation)
Access Control
There are two dimentions of access control:
-
Page level (Controller level) access control.
-
Edgar::UserGroup with kind==ROLE and Edgar::ModelPermission
represents access control on each controller. -
Admin user, who belongs to 'admin' user_group, can access any page.
-
When a user belongs to a user_group (kind==ROLE) and a model_permission belongs to the user_group, the user can access the controller which model_class is the model in model_permission.
-
More precisely, 4 kind of access controls, CREATE, READ, UPDATE, and DELETE can be set with any conbination on the controller.
-
See Edgar::ModelPermission for more detail.
-
-
Data scope.
-
data scope access is controlled by 'user_scoped(user, context)' scope defined at each model.
-
Where, user is currently accessing person to the model.
-
context is any kind of 2nd parameter. Default is session object of Edgar::Sssn, but it can be overwritten 'scope_context' method at the target controller.
-
See Author.user_scoped as an example.
-
SEE ALSO
- EdgarPopupController
-
'belongs_to' popup for EdgarController
Direct Known Subclasses
Edgar::AddressesController, Edgar::ModelPermissionsController, Edgar::SssnsController, Edgar::UserGroupUsersController, Edgar::UserGroupsController, Edgar::UsersController, Edgar::ZipAddressesController
Constant Summary
- READ_ACTIONS =
%w( index show clear search search_clear search_save search_load zip_complete csv_download file_download map view_status_save)
Instance Method Summary (collapse)
-
- (Object) clear
Ajax method to clear form.
-
- (Object) create
save new record.
-
- (Object) csv_download
download model under current condition.
-
- (Object) destroy
Permission
ModelPermission::DELETE on this controller is required.
-
- (Object) draw_flash(page)
private
At public action, just set any message into @flash_notice and/or @flash_error.
-
- (Object) enum_cache_stat
private
# Since 'render :text=>proc...' cannot be tested at functional test.
-
- (Object) file_download
To prevent unassociated file access, do:.
-
- (Object) index
draw search result in whole page.
-
- (Object) map
draw Google map.
-
- (Object) model_class
private
derive model class from this controller.
-
- (Object) model_name
private
return model name.
-
- (Object) on_upsert
private
additional behavior on upsert (default does nothing).
-
- (Object) search
Ajax method to execute search.
-
- (Object) search_clear
Ajax method to clear search conditions.
-
- (Object) search_load
Ajax method to load search condition, lines, order_by, dir, and page.
-
- (Object) search_save
Ajax method to save search conditions.
-
- (Object) show
Show detail of one record.
-
- (Object) top
This page is for following purposes:.
-
- (Object) update
save existence modified record.
-
- (Object) upsert(&block)
private
update/insert common.
-
- (Object) upsert_files
private
insert/update uploaded file and point to it via file_NN column.
- - (Object) view_status private
-
- (Object) zip_complete
zip -> address completion.
Methods included from Edgar::RescueMixin
included, #rescue_404, #rescue_edgar_error
Methods included from Edgar::PermissionMixin
#current_model_permissions, #current_user_roles, included, #require_create_permission, #require_delete_permission, #require_other_permission, #require_read_permission, #require_update_permission, #require_x_permission, #respond_to_permission_error
Methods included from Edgar::ControllerCommon
#prepare_list, #scope_context, #user_scoped, #view_status_save
Instance Method Details
- (Object) clear
Ajax method to clear form
Permission
ModelPermission::READ on this controller is required.
185 186 187 |
# File 'app/controllers/edgar_controller.rb', line 185 def clear @model = model_class.new end |
- (Object) create
save new record
Permission
ModelPermission::CREATE on this controller is required.
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'app/controllers/edgar_controller.rb', line 113 def create upsert do # NOTE: create!() is not used because assign to @model to draw form. # Otherwise, @model would be in nil so failure at edgar/_form rendering. # # NOTE2: valid? after create() calls validate_on_update. This is not # an expected behavior. So, new, valid?, then save. @model = model_class.new(params[model_name]) on_upsert upsert_files raise ActiveRecord::RecordNotSaved if !@model.valid? @model.save # clear @model values for next data-entry @model = model_class.new end end |
- (Object) csv_download
download model under current condition
respond_to...format.csv approach was not used since @list is
different as follows:
-
csv returns all of records under the conditions
-
HTML returns just in specified 'page'.
Permission
ModelPermission::READ on this controller is required.
FIXME: file.close(true) deletes files BEFORE actually send file so that comment it out. Need to clean these work files.
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'app/controllers/edgar_controller.rb', line 304 def csv_download filename = sprintf("%s-%s.csv", model_name, Time.now.strftime("%Y%m%d-%H%M%S")) file = Tempfile.new(filename, Rails.root + Edgar::CSV_DIR) csv_visitor = EdgarHelper::CsvVisitor.new(view_context) find_args = {:conditions=>view_status.model.conditions} if !view_status.order_by.blank? find_args.merge!(:order => view_status.order_by + ' ' + view_status.dir) end file.write CSV.generate_line(model_class.columns.map{|c| c.name}) for rec in user_scoped.find(:all, find_args) do array = [] for col in model_class.columns do array << csv_visitor.visit_column(rec, col) end file.write CSV.generate_line(array) end file.close send_file(file.path, { type: 'text/csv', filename: filename}) #file.close(true) end |
- (Object) destroy
Permission
ModelPermission::DELETE on this controller is required.
171 172 173 174 175 176 177 178 |
# File 'app/controllers/edgar_controller.rb', line 171 def destroy m = model_class.find(user_scoped.find(params[:id]).id) m.destroy prepare_list @model = model_class.new @flash_notice = t('delete_ok') end |
- (Object) draw_flash(page) (private)
At public action, just set any message into @flash_notice and/or @flash_error. Then, any render in EdgarController will display it by calling draw_flash().
draw_flash() must be a helper because it is called in render block, which is a kind of view.
420 421 422 423 |
# File 'app/controllers/edgar_controller.rb', line 420 def draw_flash(page) page.replace_html 'flash_notice', :text=>@flash_notice page.replace_html 'flash_error', :text=>@flash_error end |
- (Object) enum_cache_stat (private)
# Since 'render :text=>proc...' cannot be tested at functional test
# (see http://lightyearsoftware.com/2009/10/rails-bug-with-render-text-proc-in-tests/), # move the logic inside the proc here to test def csv_proc(output) csv_visitor = EdgarHelper::CsvVisitor.new(@template) find_args = {:conditions=>view_status.model.conditions} if !view_status.order_by.blank? find_args.merge!(:order => view_status.order_by + ' ' + view_status.dir) end output.write CSV.generate_line(model_class.columns.map{|c| c.name}) + "\n" for rec in model_class.find(:all, find_args) do array = [] for col in model_class.columns do array << csv_visitor.visit_column(rec, col) end output.write CSV.generate_line(array) + "\n" end end
451 452 453 454 |
# File 'app/controllers/edgar_controller.rb', line 451 def enum_cache_stat logger.debug 'Edgar::EnumCache stat (hit/out/out_of_enum): ' + Edgar::EnumCache.instance.stat.inspect end |
- (Object) file_download
To prevent unassociated file access, do:
-
check if it is in model object
-
check if it is a edgar_file column
Permission
ModelPermission::READ on this controller is required.
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'app/controllers/edgar_controller.rb', line 336 def file_download if !model_class.edgar_file?(params[:column]) flash[:error] = t('edgar_file.no_assoc') return end file_info_id = user_scoped.find(params[:id]).send(params[:column]) if file_info_id file_info = FileInfo.find(file_info_id) if file_info send_file(file_info.full_filename, :filename => file_info.filename) return end end logger.warn 'invalid file_info' end |
- (Object) index
101 102 103 104 105 106 |
# File 'app/controllers/edgar_controller.rb', line 101 def index #clear_topic_path prepare_list @search = view_status.model @model = model_class.new end |
- (Object) map
draw Google map.
Permission
ModelPermission::READ on this controller is required.
358 359 360 |
# File 'app/controllers/edgar_controller.rb', line 358 def map render :template=>'edgar/map', :layout=>false end |
- (Object) model_class (private)
derive model class from this controller.
If controller cannot guess model class, overwrite this.
Examples:
-
AuthorsController -> Author
-
Edgar::UsersController -> Edgar::User
370 371 372 |
# File 'app/controllers/edgar_controller.rb', line 370 def model_class @_model_class ||= self.class.name.gsub(/Controller$/, '').singularize.constantize end |
- (Object) model_name (private)
return model name.
if each concreate controller cannot guess model name from its controller name, overwrite this.
Examples:
-
Edgar::UsersController -> 'edgar_user'
-
AuthorsController -> 'author'
383 384 385 |
# File 'app/controllers/edgar_controller.rb', line 383 def model_name @_model_name ||= ActiveModel::Naming.param_key(model_class.new) end |
- (Object) on_upsert (private)
additional behavior on upsert (default does nothing).
Derived controller may overwrite this method if necessary, for example:
-
to upsert protected attributes.
-
to upsert server-side calculated values
392 393 394 |
# File 'app/controllers/edgar_controller.rb', line 392 def on_upsert # end |
- (Object) search
Ajax method to execute search
Actually, this doesn't execute search. Rather, this just saves condition. Search will be executed at any listing action like 'index', 'create', or 'update'.
Permission
ModelPermission::READ on this controller is required.
198 199 200 201 202 203 204 205 |
# File 'app/controllers/edgar_controller.rb', line 198 def search vc = view_status vc.model = Edgar::SearchForm.new(model_class, params[:edgar_search_form]) vc.update_attribute(:page, 1) vc.save! @search = vc.model prepare_list if @search.valid? end |
- (Object) search_clear
Ajax method to clear search conditions
Permission
ModelPermission::READ on this controller is required.
212 213 214 |
# File 'app/controllers/edgar_controller.rb', line 212 def search_clear @search = Edgar::SearchForm.new(model_class) end |
- (Object) search_load
Ajax method to load search condition, lines, order_by, dir, and page.
255 256 257 258 |
# File 'app/controllers/edgar_controller.rb', line 255 def search_load @search = current_user.saved_view_statuss.find(params[:id]).load(@sssn).model draw_search_form end |
- (Object) search_save
Ajax method to save search conditions
call flow
Edgar.SearchSavePopup.open() (javascript) (show $('search_save_popup')) Edgar.SearchSavePopup.submit() (javascript) (copy entered name into $('saved_view_status_name') in form) call :action=>'search_save'
TRICKY PART
There are two requirements:
-
use modal-dialog to enter name to decrese busy in search form.
-
send Search Form with name to server.
To comply these, Edgar.SearchSavePopup copies the entered name to 'saved_view_status_name' hidden field and then sends the form which includes the copied name.
Permission
ModelPermission::READ on this controller is required.
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'app/controllers/edgar_controller.rb', line 237 def search_save svc = SavedVcontext.save(current_user, nil, params[:saved_view_status_name], view_status) render :update do |page| page << "Edgar.SearchSavePopup.close();" page.replace 'edgar_load_condition_menu', :partial=>'edgar/load_condition_menu' end rescue ActiveRecord::ActiveRecordError => ex app_rescue render :update do |page| page.replace_html 'search_save_popup_flash_error', :text=>t('save_fail') end end |
- (Object) show
Show detail of one record. Format of html & js should be supported.
Permission
ModelPermission::READ on this controller is required.
136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'app/controllers/edgar_controller.rb', line 136 def show @model = user_scoped.find(params[:id]) #add_topic_path respond_to do |format| format.html { prepare_list @search = view_status.model render :action=>'index' } format.js end end |
- (Object) top
This page is for following purposes:
-
top page which contain latest info (TBD)
-
any error message on HTML format access
-
on Ajax access, rather edgar_error_popup is used
-
84 85 86 |
# File 'app/controllers/edgar_controller.rb', line 84 def top render :action => 'top' end |
- (Object) update
save existence modified record
Permission
ModelPermission::UPDATE on this controller is required.
154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'app/controllers/edgar_controller.rb', line 154 def update upsert do # NOTE: # 1. update ++then++ valid to set new values in @model to draw form. # 1. user_scoped may have joins so that record could be # ActiveRecord::ReadOnlyRecord so that's why access again from # model_class. @model = model_class.find(user_scoped.find(params[:id]).id) upsert_files if !@model.update_attributes(params[model_name]) raise ActiveRecord::RecordInvalid.new(@model) end end end |
- (Object) upsert(&block) (private)
update/insert common
397 398 399 400 401 402 403 404 405 406 407 |
# File 'app/controllers/edgar_controller.rb', line 397 def upsert(&block) ActiveRecord::Base.transaction do yield @flash_notice = t('save_ok') end prepare_list rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid => ex logger.info "#{ex.class.to_s}: #{@model.class.to_s}: #{@model.errors..join(' / ')}" app_rescue @flash_error = t('save_fail') end |
- (Object) upsert_files (private)
insert/update uploaded file and point to it via file_NN column
426 427 428 |
# File 'app/controllers/edgar_controller.rb', line 426 def upsert_files @model.upsert_files(params[:file_info], params[model_name]) end |
- (Object) view_status (private)
409 410 411 412 |
# File 'app/controllers/edgar_controller.rb', line 409 def view_status @view_status ||= Edgar::ViewStatus.intern(@sssn, controller_name, Edgar::SearchForm.new(model_class)) end |
- (Object) zip_complete
zip -> address completion
INPUTS
- params
-
key to find address info. hyphen is supported.
- params
-
address fields DOM id prefix. e.g. 'org_adrs_0_'
call flow
on drawing
EdgarHelper.draw_adrs() app/helpers/edgar_helper.rb
app/views/edgar/_address.html.erb
Example:
:
<input type=text name='org[adrs_0_zip]'>
:
on clicking zip->address button
Edgar.zip_complete() public/javascripts/edgar.js
Ajax.Request()
EdgarController.zip_complete app/controllers/edgar_controller.rb
inline RJS to fill address info
281 282 283 284 285 286 287 288 289 290 |
# File 'app/controllers/edgar_controller.rb', line 281 def zip_complete zip = params[:zip].gsub(/\D/, '') @address = Edgar::ZipAddress.find_by_zip(zip) || Edgar::ZipAddress.new( :prefecture => '?', :city => '', :other => '') # sanitize @adrs_prefix = params[:adrs_prefix].gsub(/[^a-zA-Z_0-9]/, '') end |