initial
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
<!--- *** Refactoring required --->
|
||||
<!--- v0.3
|
||||
2017-08-30
|
||||
Настройки вынесены в таблицу usr_settings в связи с миграцией репозитория пользователей в мастер справочники
|
||||
Заодно cfqueryparam
|
||||
|
||||
модуль должен инкапсулировать работу с настройками
|
||||
- хранение в сессии и в БД, синхронность с базой
|
||||
- чтение и модификацию
|
||||
- очистку(сброс)
|
||||
При этом модуль ничего не должен знать о структуре самих настроек (фильтрах, сортировках и т.д.)
|
||||
|
||||
Жизненный цикл контейнера settings
|
||||
1. Живет в сессии
|
||||
2. Если в сессии нет settings - вычитывается из БД
|
||||
3. Если и в БД нет - инициализируется и сразу сохраняется в БД (пустой)
|
||||
4. При модификации синхронизируется с базой (пишет в нее) - нужно либо снаружи вызывать сохранение, либо инкапсулировать запись
|
||||
(инкапсулировать запись - сильно усложнит код)
|
||||
5. Для работы выдает КОПИЮ данных, соответственно, при сохранении должен ее синхронизировать???
|
||||
Пока решили, что будем выдавать ссылку на объект в сессии, при этом нужно блок кода синхронизировать CFLOCK
|
||||
При этом 2 проблемы:
|
||||
- некрасивый код (лок на сессию руками, связь совершенно неявная)
|
||||
- избыточная блокировка
|
||||
- избыточное сохранение (можно было бы проверять на изменение)
|
||||
чтобы действовать без блокировки, можно экспортировать объект в безопасный scope - переменные или реквест
|
||||
Альтернативный синтаксис вызова
|
||||
<m:settings key="page_name" output="settings"><!--- implicit get --->
|
||||
<m:sortparam sort=#settings.sort# fieldcount=9 sortlength=2/><!--- any modification --->
|
||||
</m:settings><!--- implicit sync --->
|
||||
|
||||
|
||||
Разрешение конфликта версий при обновлении софта
|
||||
Конфликт версий: код требует параметр, отсутствующий в сохраненной версии. Например, одна версия фильтра сохранила свои настройки,
|
||||
а другая, более новая, пытается прочесть несуществующий ключ.
|
||||
Другой вариант - несуществующее поле (убранное в очередной версии) - этот конфликт проявится только при запросе к БД.
|
||||
Варианты решения
|
||||
- создание параметра с дефолтными значениями при запросе (во все запросы добавить значения по умолчанию). Недостаток - возможно создание помойки
|
||||
- контроль номера версии. Выглядит сложным в сопровождении
|
||||
*** Возможно, это была плохая идея - разрешить доступ по длинному ключу. Когда доставали и записывали целиком объект, было меньше возможностей для ошибок
|
||||
--->
|
||||
|
||||
<!---*** обработка вызова get-sync c закрывающим тегом --->
|
||||
<cfparam name="ATTRIBUTES.action" type="string" default=""/>
|
||||
<cfparam name="obj" type="struct" default=#structNew()#/>
|
||||
<cfparam name="doSyncOnClose" type="boolean" default="No"/>
|
||||
|
||||
<cfif thisTag.executionMode IS "start">
|
||||
<cfif ATTRIBUTES.action IS "">
|
||||
<cfset ATTRIBUTES.action="get"/>
|
||||
<cfset doSyncOnClose="Yes"/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfif doSyncOnClose>
|
||||
<cfset ATTRIBUTES.action="sync"/>
|
||||
<cfelse>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfset useDB="false"/><!--- --->
|
||||
|
||||
<cfswitch expression=#lCase(ATTRIBUTES.action)#>
|
||||
|
||||
<cfcase value="create"> <!--- конструктор --->
|
||||
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||||
<cfset session.settings = retrieveSettings()>
|
||||
</cflock>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="sync">
|
||||
<!--- Синхронизировать измененный объект (с сессией и персистенсом) --->
|
||||
<cfparam name="ATTRIBUTES.key" type="string">
|
||||
<cfif doSyncOnClose>
|
||||
<cfif thisTag.executionMode IS "end">
|
||||
<cfset obj=StructFind(CALLER, "#ATTRIBUTES.output#")/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfparam name="ATTRIBUTES.obj" type="struct">
|
||||
<cfset obj=#ATTRIBUTES.obj#/>
|
||||
</cfif>
|
||||
<!---<cfdump var=#obj#/>--->
|
||||
|
||||
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||||
<!--- помещаем объект на свое место в структуре --->
|
||||
<cfset container=#session.settings#>
|
||||
<cfset cnt = listLen("#ATTRIBUTES.key#", ".")>
|
||||
<cfloop index="i" from=1 to=#cnt#>
|
||||
<cfset item = listGetAt(#ATTRIBUTES.key#, i, ".")>
|
||||
<cfif i EQ cnt><!--- last element --->
|
||||
<cfset structInsert(#container#, #item#, #obj#, "YES")>
|
||||
<cfelse>
|
||||
<!--- попытка перебдеть--->
|
||||
<cfif NOT structKeyExists(#container#,#item#)>
|
||||
<cfset structInsert(#container#,#item#,#structNew()#)>
|
||||
</cfif>
|
||||
<cfset container=structFind(container,item)>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cflock>
|
||||
<!--- сохраняем настройки в персистенс --->
|
||||
<cfset saveSettings(container)>
|
||||
</cfcase>
|
||||
|
||||
|
||||
<cfcase value="reset"><!--- *** надо было назвать reset_all или как-то так --->
|
||||
<!--- Сбросить все пользовательские настройки --->
|
||||
<cfset session.settings=structNew()>
|
||||
<cfset saveSettings(session.settings)>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="clear">
|
||||
<!--- Очистить секцию пользовательских настроек (sort или filter)--->
|
||||
<cfparam name="ATTRIBUTES.key" type="string">
|
||||
<cfset targetFullName = "session.settings.#ATTRIBUTES.key#">
|
||||
<cfset objName = listGetAt(ATTRIBUTES.key,1,".")>
|
||||
<cfif isDefined(targetFullName)>
|
||||
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||||
<cfset evaluate("structClear(#targetFullName#)")>
|
||||
<cfset obj = evaluate(#targetFullName#)>
|
||||
<cfmodule template="settings.cfm" action="sync" key=#ATTRIBUTES.key# obj=#obj#><!--- синхронизация при очистке используется ---><!--- хорошо ли это? А вообще надо?--->
|
||||
</cflock>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="get">
|
||||
<!---
|
||||
Получить объект или секцию
|
||||
Обязательный параметр key - имя ключа в структуре, начинающееся с имени объекта, через точку
|
||||
В параметре output имя переменной, принимающей результат
|
||||
--->
|
||||
<cfparam name="ATTRIBUTES.key" type="string">
|
||||
<cfparam name="ATTRIBUTES.output" type="string">
|
||||
|
||||
<cfif NOT isDefined("session.settings")><!--- *** container--->
|
||||
<cfmodule template="settings.cfm" action="create"/>
|
||||
</cfif>
|
||||
|
||||
<cfset targetFullName = "session.settings.#ATTRIBUTES.key#">
|
||||
<cfset objName = listGetAt(ATTRIBUTES.key, 1, ".")>
|
||||
<cfif (NOT structKeyExists(#session.settings#, objName))
|
||||
OR structIsEmpty(structFind(#session.settings#, objName))>
|
||||
<cfmodule template="init_settings.cfm" container=#session.settings# objName=#objName#/>
|
||||
<!--- несколько неуклюже, лучше просто возвращать объект и потом класть в сессию --->
|
||||
</cfif>
|
||||
<cftry>
|
||||
<cfset obj=evaluate(targetFullName)>
|
||||
<cfcatch type="ANY">
|
||||
<cfmodule template="init_settings.cfm" container=#session.settings# objName=#objName#/><!---*** грубо --->
|
||||
<cfset obj=evaluate(targetFullName)>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#", #duplicate(obj)#)>
|
||||
</cfcase>
|
||||
<cfdefaultcase>
|
||||
<cfthrow message="Unsupported action = #ATTRIBUTES.action# in settings.cfm">
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
<!---***debug <cfoutput>#ATTRIBUTES.action# #thisTag.executionMode# #thisTag.hasendtag#</cfoutput>--->
|
||||
|
||||
|
||||
|
||||
<!--- --------------------------------------------------------------------------------- --->
|
||||
<!--- private methods --->
|
||||
<cffunction name="retrieveSettings" access="private">
|
||||
<cfif useDB><!--- можно перегрузить методы для разных провайдеров персистенса, но пока смысла в этом не видно, код достаточно примитивен и управляем --->
|
||||
<cfquery name="qGet" datasource="#request.DS#">
|
||||
select settings from usr where usr_id='#request.usr_id#'
|
||||
</cfquery>
|
||||
<cfif len(qGet.settings) GT 0>
|
||||
<cftry>
|
||||
<CFWDDX ACTION='wddx2cfml' input=#qGet.settings# output='settings'/>
|
||||
<cfreturn #settings#/>
|
||||
<cfcatch type="Any">Err:
|
||||
<cfoutput>#cfcatch.message#</cfoutput>
|
||||
<cfabort/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfreturn #structNew()#/>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="saveSettings" access="private">
|
||||
<cfargument name=settings>
|
||||
|
||||
<cfif !isNumeric(request.usr_id)
|
||||
OR !(request.usr_id GT 0)
|
||||
OR request.usr_id EQ request.ANONYMOUS_USR_ID
|
||||
OR request.usr_id EQ request.UNDEFINED_USR_ID>
|
||||
<cfreturn />
|
||||
</cfif>
|
||||
|
||||
<cfif useDB>
|
||||
<cfquery name="qCheckExistence" datasource="#request.DS#">
|
||||
select count(*) as cnt from usr_serrings where usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>
|
||||
</cfquery>
|
||||
|
||||
<CFWDDX ACTION='cfml2wddx' input=#session.settings# output='strSettings'/>
|
||||
|
||||
<cfif qCheckExistence.cnt EQ 0>
|
||||
<cfquery name="qSave" datasource="#request.DS#">
|
||||
insert int usr_settings (usr_id, settings)
|
||||
values(<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>, <cfqueryparam cfsqltype="cf_sql_varchar" value='#strSettings#'/>)
|
||||
</cfquery>
|
||||
<cfelse>
|
||||
<cfquery name="qSave" datasource="#request.DS#">
|
||||
update usr_settings
|
||||
set settings=<cfqueryparam cfsqltype="cf_sql_varchar" value='#strSettings#'/>
|
||||
where usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>
|
||||
</cfquery>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<!--- do nothing --->
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
Reference in New Issue
Block a user