spiderfw 0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +4 -0
- data/Rakefile +74 -0
- data/apps/cas_server/_init.rb +23 -0
- data/apps/cas_server/config/options.rb +9 -0
- data/apps/cas_server/controllers/mixins/cas_login_mixin.rb +353 -0
- data/apps/cas_server/lib/cas.rb +329 -0
- data/apps/cas_server/lib/utils.rb +30 -0
- data/apps/cas_server/models/login_ticket.rb +11 -0
- data/apps/cas_server/models/mixins/consumable.rb +10 -0
- data/apps/cas_server/models/proxy_granting_ticket.rb +12 -0
- data/apps/cas_server/models/proxy_ticket.rb +12 -0
- data/apps/cas_server/models/service_ticket.rb +22 -0
- data/apps/cas_server/models/ticket.rb +20 -0
- data/apps/cas_server/models/ticket_granting_ticket.rb +15 -0
- data/apps/cas_server/views/logout.shtml +3 -0
- data/apps/core/admin/_init.rb +23 -0
- data/apps/core/admin/admin.rb +17 -0
- data/apps/core/admin/controllers/admin_controller.rb +15 -0
- data/apps/core/admin/views/index.shtml +1 -0
- data/apps/core/admin/views/spider_admin.layout.shtml +23 -0
- data/apps/core/admin/widgets/admin_menu/admin_menu.rb +17 -0
- data/apps/core/auth/_init.rb +42 -0
- data/apps/core/auth/controllers/login_controller.rb +94 -0
- data/apps/core/auth/controllers/mixins/auth_helper.rb +114 -0
- data/apps/core/auth/controllers/mixins/http_basic_auth.rb +24 -0
- data/apps/core/auth/controllers/mixins/http_digest_auth.rb +26 -0
- data/apps/core/auth/lib/authenticable.rb +86 -0
- data/apps/core/auth/lib/authenticator.rb +11 -0
- data/apps/core/auth/lib/digest_authenticator.rb +44 -0
- data/apps/core/auth/lib/login_authenticator.rb +27 -0
- data/apps/core/auth/models/digest_user.rb +34 -0
- data/apps/core/auth/models/group.rb +22 -0
- data/apps/core/auth/models/login_user.rb +15 -0
- data/apps/core/auth/models/mixins/access_control.rb +71 -0
- data/apps/core/auth/models/mixins/authentication_tracking.rb +19 -0
- data/apps/core/auth/models/super_user.rb +8 -0
- data/apps/core/auth/models/user.rb +15 -0
- data/apps/core/auth/po/it/spider_auth.po +33 -0
- data/apps/core/auth/po/spider_auth.pot +31 -0
- data/apps/core/auth/views/login.shtml +25 -0
- data/apps/core/components/_init.rb +21 -0
- data/apps/core/components/po/it/spider_components.po +52 -0
- data/apps/core/components/po/spider_components.pot +53 -0
- data/apps/core/components/public/css/admin.css +73 -0
- data/apps/core/components/public/css/crud.css +58 -0
- data/apps/core/components/public/css/img/add.gif +0 -0
- data/apps/core/components/public/css/img/ajax-loader.gif +0 -0
- data/apps/core/components/public/css/img/back.gif +0 -0
- data/apps/core/components/public/css/img/bg_header.png +0 -0
- data/apps/core/components/public/css/img/body_bg.jpg +0 -0
- data/apps/core/components/public/css/img/gray_gradient.gif +0 -0
- data/apps/core/components/public/css/img/li_bg.png +0 -0
- data/apps/core/components/public/css/img/li_bg_active.png +0 -0
- data/apps/core/components/public/css/img/li_bg_hover.png +0 -0
- data/apps/core/components/public/css/img/logo.png +0 -0
- data/apps/core/components/public/css/img/menu_bg.png +0 -0
- data/apps/core/components/public/css/img/menu_bottom.png +0 -0
- data/apps/core/components/public/css/img/menu_top.png +0 -0
- data/apps/core/components/public/css/img/section-bottom.jpg +0 -0
- data/apps/core/components/public/css/list.css +7 -0
- data/apps/core/components/public/css/menu.css +47 -0
- data/apps/core/components/public/css/spider.css +9 -0
- data/apps/core/components/public/css/switcher.css +13 -0
- data/apps/core/components/public/css/table.css +99 -0
- data/apps/core/components/public/js/inheritance.js +71 -0
- data/apps/core/components/public/js/jquery/jquery-1.3.2.js +4376 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/smoothness/jquery-ui-1.7.2.custom.css +406 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-icons_222222_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-icons_228ef1_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-icons_ef8c08_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-icons_ffd27a_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/images/ui-icons_ffffff_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/css/ui-lightness/jquery-ui-1.7.2.custom.css +406 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/AUTHORS.txt +30 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/GPL-LICENSE.txt +278 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/MIT-LICENSE.txt +25 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/bgiframe/ChangeLog.txt +20 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/bgiframe/META.json +32 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/bgiframe/jquery.bgiframe.js +100 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/bgiframe/jquery.bgiframe.min.js +10 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/bgiframe/jquery.bgiframe.pack.js +10 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/bgiframe/test/index.html +197 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/cookie/jquery.cookie.js +97 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/cookie/jquery.cookie.min.js +10 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/cookie/jquery.cookie.pack.js +10 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/cookie/jquery.cookie.zip +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/jsdiff/jsdiff.js +159 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/qunit/testrunner.js +780 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/qunit/testsuite.css +120 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/external/simulate/jquery.simulate.js +152 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/jquery-1.3.2.js +4376 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-icons_222222_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-icons_2e83ff_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-icons_454545_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-icons_888888_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.accordion.css +9 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.all.css +2 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.base.css +8 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.core.css +37 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.datepicker.css +62 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.dialog.css +13 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.progressbar.css +4 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.resizable.css +13 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.slider.css +17 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.tabs.css +11 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/base/ui.theme.css +245 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-bg_flat_10_000000_40x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-icons_222222_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-icons_228ef1_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-icons_ef8c08_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-icons_ffd27a_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/images/ui-icons_ffffff_256x240.png +0 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/jquery-ui-1.7.2.custom.css +406 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.accordion.css +9 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.all.css +2 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.base.css +8 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.core.css +37 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.datepicker.css +62 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.dialog.css +13 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.progressbar.css +4 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.resizable.css +13 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.slider.css +17 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.tabs.css +11 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/themes/ui-lightness/ui.theme.css +247 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.blind.js +49 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.bounce.js +78 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.clip.js +54 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.core.js +545 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.drop.js +50 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.explode.js +79 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.fold.js +56 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.highlight.js +48 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.pulsate.js +56 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.scale.js +180 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.shake.js +57 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.slide.js +50 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/effects.transfer.js +45 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/jquery-ui-i18n.js +771 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-ar.js +20 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-bg.js +20 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-ca.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-cs.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-da.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-de.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-el.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-eo.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-es.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-fa.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-fi.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-fr.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-he.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-hr.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-hu.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-hy.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-id.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-is.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-it.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-ja.js +20 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-ko.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-lt.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-lv.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-ms.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-nl.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-no.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-pl.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-pt-BR.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-ro.js +22 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-ru.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-sk.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-sl.js +20 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-sq.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-sr-SR.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-sr.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-sv.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-th.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-tr.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-uk.js +25 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-zh-CN.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-zh-TW.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/jquery-ui-1.7.2.custom.js +9133 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.accordion.js +477 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.core.js +519 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.datepicker.js +1636 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.dialog.js +671 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.draggable.js +766 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.droppable.js +282 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.progressbar.js +116 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.resizable.js +800 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.selectable.js +257 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.slider.js +558 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.sortable.js +1019 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/ui/ui.tabs.js +685 -0
- data/apps/core/components/public/js/jquery/jquery-ui/development-bundle/version.txt +1 -0
- data/apps/core/components/public/js/jquery/jquery-ui/index.html +367 -0
- data/apps/core/components/public/js/jquery/jquery-ui/js/jquery-1.3.2.min.js +19 -0
- data/apps/core/components/public/js/jquery/jquery-ui/js/jquery-ui-1.7.2.custom.min.js +298 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/changelog.txt +27 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/jquery.autocomplete.css +48 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/jquery.autocomplete.js +808 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/jquery.autocomplete.min.js +13 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/jquery.autocomplete.pack.js +12 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/lib/jquery.ajaxQueue.js +116 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/lib/jquery.bgiframe.min.js +10 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/lib/jquery.js +3558 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/lib/thickbox-compressed.js +10 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/lib/thickbox.css +163 -0
- data/apps/core/components/public/js/jquery/plugins/jquery-autocomplete/todo +166 -0
- data/apps/core/components/public/js/jquery/plugins/jquery.form.js +653 -0
- data/apps/core/components/public/js/jquery/plugins/jquery.query-2.1.6.js +224 -0
- data/apps/core/components/public/js/jquery/plugins/jquery.url.js +214 -0
- data/apps/core/components/public/js/jquery/plugins/jtree/jquery.jtree.1.0.js +187 -0
- data/apps/core/components/public/js/jquery/plugins/jtree/jquery.jtree.1.0.min.js +1 -0
- data/apps/core/components/public/js/jquery/plugins/jtree/jquery.jtree.spider.1.0.js +193 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/changelog.txt +35 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/ajax-loader.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/file.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/folder-closed.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/folder.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/minus.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/plus.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-black-line.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-black.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-default-line.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-default.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-famfamfam-line.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-famfamfam.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-gray-line.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-gray.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-red-line.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/images/treeview-red.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/jquery.treeview.async.js +110 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/jquery.treeview.css +74 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/jquery.treeview.edit.js +37 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/jquery.treeview.js +256 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/jquery.treeview.min.js +15 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/jquery.treeview.pack.js +16 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/jquery.treeview.sortable.js +386 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/lib/jquery.cookie.js +92 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/lib/jquery.js +3534 -0
- data/apps/core/components/public/js/jquery/plugins/treeview/todo +8 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/LICENSE +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/README +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/jquery.timepickr.css +34 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/jquery.timepickr.js +1413 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/jquery.timepickr.min.js +10 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/222222_256x240_icons_icons.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/2e83ff_256x240_icons_icons.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/454545_256x240_icons_icons.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/888888_256x240_icons_icons.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/aaaaaa_40x100_textures_01_flat_0.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/cccccc_40x100_textures_03_highlight_soft_75.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/cd0a0a_256x240_icons_icons.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/dadada_40x100_textures_02_glass_75.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/e6e6e6_40x100_textures_02_glass_75.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/fbf9ee_40x100_textures_02_glass_55.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/fef1ec_40x100_textures_05_inset_soft_95.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/ffffff_40x100_textures_02_glass_65.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/images/ffffff_40x100_textures_02_glass_75.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.accordion.css +9 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.all.css +4 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.allplugins.css +7 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.core.css +37 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.datepicker.css +60 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.dialog.css +13 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.progressbar.css +4 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.resizable.css +13 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.slider.css +13 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.tabs.css +9 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.theme.css +238 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/themes/default/ui.timepickr.css +44 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/ui.timepickr.js +2307 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/dist/ui.timepickr.min.js +10 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/body-bg.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/btn-hover.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/btn.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/buttons.psd +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/index.html +326 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/jquery.anchorHandler.js +44 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/jquery.strings.js +290 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/jquery.ui.all.js +518 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/jquery.utils.js +255 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/jquery.utils.ui.min.js +26 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/logo-ff.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/logo-ie.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/logo.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/logo.psd +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/package.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/reset.css +48 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/script_code.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/styles.css +308 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/themeroller_ready_black_200px.gif +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/page/weather_sun.png +0 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/src/css/ui.dropslide.css +26 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/src/css/ui.timepickr.css +18 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/src/ui.dropslide.js +132 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/src/ui.timepickr.js +217 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/test/data/testrunner.js +1 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/test/index.html +34 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/test/otherlibs/jquery/1.2.1/jquery.js +11 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/test/otherlibs/jquery/1.2.3/jquery.js +11 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/test/qunit/testrunner.js +780 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/test/qunit/testsuite.css +120 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/test/styles.css +201 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/test/test.js +41 -0
- data/apps/core/components/public/js/jquery/plugins/ui-timepickr/test/unit/ui.timepickr.js +154 -0
- data/apps/core/components/public/js/list.js +234 -0
- data/apps/core/components/public/js/spider.js +484 -0
- data/apps/core/components/public/widgets/search_table.js +10 -0
- data/apps/core/components/public/widgets/table.js +9 -0
- data/apps/core/components/widgets/admin/admin.rb +75 -0
- data/apps/core/components/widgets/admin/admin.shtml +16 -0
- data/apps/core/components/widgets/confirm/confirm.rb +37 -0
- data/apps/core/components/widgets/confirm/confirm.shtml +9 -0
- data/apps/core/components/widgets/crud/crud.rb +119 -0
- data/apps/core/components/widgets/crud/crud.shtml +59 -0
- data/apps/core/components/widgets/crud/form_table/form_table.rb +13 -0
- data/apps/core/components/widgets/crud/form_table/form_table.shtml +3 -0
- data/apps/core/components/widgets/list/list.rb +294 -0
- data/apps/core/components/widgets/list/list.shtml +38 -0
- data/apps/core/components/widgets/list/plugins/exportable/exportable_list.rb +19 -0
- data/apps/core/components/widgets/list/plugins/exportable/exportable_list.shtml +5 -0
- data/apps/core/components/widgets/menu/menu.rb +20 -0
- data/apps/core/components/widgets/menu/menu.shtml +13 -0
- data/apps/core/components/widgets/search_table/search_table.rb +22 -0
- data/apps/core/components/widgets/search_table/search_table.shtml +11 -0
- data/apps/core/components/widgets/switcher/switcher.rb +68 -0
- data/apps/core/components/widgets/switcher/switcher.shtml +17 -0
- data/apps/core/components/widgets/table/table.rb +153 -0
- data/apps/core/components/widgets/table/table.shtml +41 -0
- data/apps/core/components/widgets/tabs/tabs.rb +38 -0
- data/apps/core/components/widgets/tabs/tabs.shtml +2 -0
- data/apps/core/forms/_init.rb +22 -0
- data/apps/core/forms/po/it/spider_forms.po +36 -0
- data/apps/core/forms/po/spider_forms.pot +37 -0
- data/apps/core/forms/public/date_time.js +23 -0
- data/apps/core/forms/public/form.css +81 -0
- data/apps/core/forms/public/form.js +19 -0
- data/apps/core/forms/public/input.js +30 -0
- data/apps/core/forms/public/search_select.js +123 -0
- data/apps/core/forms/public/select.js +11 -0
- data/apps/core/forms/tags/element_label.erb +1 -0
- data/apps/core/forms/tags/element_row.erb +4 -0
- data/apps/core/forms/tags/row.erb +4 -0
- data/apps/core/forms/widgets/form/form.rb +430 -0
- data/apps/core/forms/widgets/form/form.shtml +47 -0
- data/apps/core/forms/widgets/inputs/checkbox/checkbox.rb +13 -0
- data/apps/core/forms/widgets/inputs/checkbox/checkbox.shtml +3 -0
- data/apps/core/forms/widgets/inputs/date_time/date_time.rb +48 -0
- data/apps/core/forms/widgets/inputs/date_time/date_time.shtml +4 -0
- data/apps/core/forms/widgets/inputs/hidden/hidden.rb +8 -0
- data/apps/core/forms/widgets/inputs/hidden/hidden.shtml +3 -0
- data/apps/core/forms/widgets/inputs/input/input.rb +105 -0
- data/apps/core/forms/widgets/inputs/input/input.shtml +3 -0
- data/apps/core/forms/widgets/inputs/input/readonly.shtml +6 -0
- data/apps/core/forms/widgets/inputs/password/password.rb +24 -0
- data/apps/core/forms/widgets/inputs/password/password.shtml +5 -0
- data/apps/core/forms/widgets/inputs/search_select/search_select.rb +168 -0
- data/apps/core/forms/widgets/inputs/search_select/search_select.shtml +42 -0
- data/apps/core/forms/widgets/inputs/select/select.rb +126 -0
- data/apps/core/forms/widgets/inputs/select/select.shtml +8 -0
- data/apps/core/forms/widgets/inputs/subform/subform.rb +10 -0
- data/apps/core/forms/widgets/inputs/subform/subform.shtml +5 -0
- data/apps/core/forms/widgets/inputs/text/text.rb +9 -0
- data/apps/core/forms/widgets/inputs/text/text.shtml +3 -0
- data/apps/core/forms/widgets/inputs/text_area/text_area.rb +10 -0
- data/apps/core/forms/widgets/inputs/text_area/text_area.shtml +1 -0
- data/apps/drb_server/_init.rb +8 -0
- data/apps/drb_server/lib/model_server.rb +41 -0
- data/apps/drb_server/script/start_server.rb +19 -0
- data/apps/hippo/_init.rb +10 -0
- data/apps/hippo/models/mixins/hippo_struct.rb +210 -0
- data/apps/hippo/models/security_group.rb +21 -0
- data/apps/hippo/models/security_user.rb +29 -0
- data/apps/messenger/_init.rb +24 -0
- data/apps/messenger/config/options.rb +15 -0
- data/apps/messenger/config/worker.rb +3 -0
- data/apps/messenger/controllers/messenger_controller.rb +80 -0
- data/apps/messenger/controllers/mixins/messenger_controller_mixin.rb +54 -0
- data/apps/messenger/lib/backends/email/smtp.rb +0 -0
- data/apps/messenger/messenger.rb +94 -0
- data/apps/messenger/models/email.rb +22 -0
- data/apps/messenger/models/message.rb +44 -0
- data/apps/messenger/po/it/spider_messenger.po +77 -0
- data/apps/messenger/po/spider_messenger.pot +77 -0
- data/apps/messenger/views/index.shtml +13 -0
- data/apps/messenger/views/list.shtml +5 -0
- data/apps/messenger/views/messenger.layout.shtml +4 -0
- data/apps/messenger/views/queue.shtml +8 -0
- data/apps/messenger/views/tags/sent_messages.shtml +1 -0
- data/apps/soap/_init.rb +16 -0
- data/apps/soap/controllers/soap_controller.rb +334 -0
- data/apps/soap/doc/examples/example.rb +28 -0
- data/apps/soap/lib/soap.rb +217 -0
- data/apps/sso/_init.rb +16 -0
- data/apps/sso/config/options.rb +13 -0
- data/apps/sso/controllers/mixins/saml2_mixin.rb +149 -0
- data/apps/sso/lib/saml2/backend.rb +16 -0
- data/apps/sso/lib/saml2/lasso_backend.rb +88 -0
- data/apps/sso/lib/saml2.rb +112 -0
- data/apps/sso/views/saml2_post.shtml +19 -0
- data/apps/webdav/_init.rb +15 -0
- data/apps/webdav/controllers/webdav_controller.rb +862 -0
- data/apps/webdav/lib/locking.rb +193 -0
- data/apps/webdav/lib/vfs/abstract.rb +78 -0
- data/apps/webdav/lib/vfs/local.rb +126 -0
- data/apps/webdav/lib/vfs/mapped.rb +235 -0
- data/apps/webdav/lib/vfs/properties.rb +41 -0
- data/apps/worker/_init.rb +13 -0
- data/apps/worker/cmd.rb +65 -0
- data/apps/worker/config/options.rb +9 -0
- data/apps/worker/lib/runner.rb +43 -0
- data/apps/worker/lib/task.rb +9 -0
- data/apps/worker/models/job.rb +20 -0
- data/apps/worker/worker.rb +168 -0
- data/bin/spider +9 -0
- data/blueprints/app/_init.rb +10 -0
- data/blueprints/app/controllers/__APP___controller.rb +14 -0
- data/blueprints/app/views/__APP__.layout.shtml +8 -0
- data/blueprints/app/views/index.shtml +3 -0
- data/blueprints/install/config/config.yml +14 -0
- data/blueprints/install/init.rb +3 -0
- data/data/locale/it/LC_MESSAGES/spider.mo +0 -0
- data/data/locale/it/LC_MESSAGES/spider_auth.mo +0 -0
- data/data/locale/it/LC_MESSAGES/spider_components.mo +0 -0
- data/data/locale/it/LC_MESSAGES/spider_forms.mo +0 -0
- data/data/locale/it/LC_MESSAGES/spider_messenger.mo +0 -0
- data/lib/spiderfw/app.rb +186 -0
- data/lib/spiderfw/autoload.rb +12 -0
- data/lib/spiderfw/cache/template_cache.rb +136 -0
- data/lib/spiderfw/cmd/cmd.rb +72 -0
- data/lib/spiderfw/cmd/commands/cert.rb +427 -0
- data/lib/spiderfw/cmd/commands/console.rb +27 -0
- data/lib/spiderfw/cmd/commands/init.rb +52 -0
- data/lib/spiderfw/cmd/commands/model.rb +131 -0
- data/lib/spiderfw/cmd/commands/setup.rb +54 -0
- data/lib/spiderfw/cmd/commands/test.rb +61 -0
- data/lib/spiderfw/cmd/commands/webserver.rb +93 -0
- data/lib/spiderfw/config/configurable.rb +38 -0
- data/lib/spiderfw/config/configuration.rb +281 -0
- data/lib/spiderfw/config/options/spider.rb +122 -0
- data/lib/spiderfw/controller/app_controller.rb +14 -0
- data/lib/spiderfw/controller/controller.rb +321 -0
- data/lib/spiderfw/controller/controller_exceptions.rb +27 -0
- data/lib/spiderfw/controller/controller_io.rb +26 -0
- data/lib/spiderfw/controller/controller_mixin.rb +12 -0
- data/lib/spiderfw/controller/cookie.rb +20 -0
- data/lib/spiderfw/controller/cookies.rb +17 -0
- data/lib/spiderfw/controller/dispatcher.rb +269 -0
- data/lib/spiderfw/controller/first_responder.rb +59 -0
- data/lib/spiderfw/controller/formats/html.rb +90 -0
- data/lib/spiderfw/controller/helpers/widget_helper.rb +73 -0
- data/lib/spiderfw/controller/home_controller.rb +26 -0
- data/lib/spiderfw/controller/http_controller.rb +112 -0
- data/lib/spiderfw/controller/mixins/http_mixin.rb +228 -0
- data/lib/spiderfw/controller/mixins/static_content.rb +90 -0
- data/lib/spiderfw/controller/mixins/visual.rb +489 -0
- data/lib/spiderfw/controller/page_controller.rb +39 -0
- data/lib/spiderfw/controller/request.rb +48 -0
- data/lib/spiderfw/controller/response.rb +50 -0
- data/lib/spiderfw/controller/router.rb +9 -0
- data/lib/spiderfw/controller/scene.rb +27 -0
- data/lib/spiderfw/controller/session/file_session.rb +83 -0
- data/lib/spiderfw/controller/session/flash_hash.rb +56 -0
- data/lib/spiderfw/controller/session/memory_session.rb +62 -0
- data/lib/spiderfw/controller/session/transient_hash.rb +18 -0
- data/lib/spiderfw/controller/session.rb +106 -0
- data/lib/spiderfw/controller/spider_controller.rb +20 -0
- data/lib/spiderfw/create.rb +64 -0
- data/lib/spiderfw/env.rb +17 -0
- data/lib/spiderfw/exceptions.rb +2 -0
- data/lib/spiderfw/home.rb +26 -0
- data/lib/spiderfw/http/adapters/cgi.rb +62 -0
- data/lib/spiderfw/http/adapters/cgi_io.rb +27 -0
- data/lib/spiderfw/http/adapters/fcgi.rb +62 -0
- data/lib/spiderfw/http/adapters/mongrel.rb +207 -0
- data/lib/spiderfw/http/adapters/rack.rb +152 -0
- data/lib/spiderfw/http/adapters/thin.rb +38 -0
- data/lib/spiderfw/http/adapters/webrick.rb +176 -0
- data/lib/spiderfw/http/http.rb +297 -0
- data/lib/spiderfw/http/server.rb +40 -0
- data/lib/spiderfw/i18n/cldr.rb +114 -0
- data/lib/spiderfw/i18n/gettext.rb +8 -0
- data/lib/spiderfw/i18n/i18n.rb +120 -0
- data/lib/spiderfw/i18n/provider.rb +12 -0
- data/lib/spiderfw/i18n/rails.rb +89 -0
- data/lib/spiderfw/i18n/shtml_parser.rb +41 -0
- data/lib/spiderfw/model/active_record.rb +335 -0
- data/lib/spiderfw/model/base_model.rb +2068 -0
- data/lib/spiderfw/model/condition.rb +416 -0
- data/lib/spiderfw/model/data_type.rb +75 -0
- data/lib/spiderfw/model/datatypes/binary.rb +10 -0
- data/lib/spiderfw/model/datatypes/bool.rb +10 -0
- data/lib/spiderfw/model/datatypes/decimal.rb +22 -0
- data/lib/spiderfw/model/datatypes/email.rb +13 -0
- data/lib/spiderfw/model/datatypes/password.rb +63 -0
- data/lib/spiderfw/model/datatypes/serialized_object.rb +32 -0
- data/lib/spiderfw/model/datatypes/text.rb +10 -0
- data/lib/spiderfw/model/datatypes/uuid.rb +29 -0
- data/lib/spiderfw/model/datatypes.rb +19 -0
- data/lib/spiderfw/model/element.rb +250 -0
- data/lib/spiderfw/model/extended_models/managed.rb +43 -0
- data/lib/spiderfw/model/identity_mapper.rb +89 -0
- data/lib/spiderfw/model/inline_model.rb +37 -0
- data/lib/spiderfw/model/mappers/db_mapper.rb +1126 -0
- data/lib/spiderfw/model/mappers/hash_mapper.rb +114 -0
- data/lib/spiderfw/model/mappers/mapper.rb +926 -0
- data/lib/spiderfw/model/mappers/mappers.rb +7 -0
- data/lib/spiderfw/model/mappers/proxy_mapper.rb +25 -0
- data/lib/spiderfw/model/mappers/vfs/flat_file.rb +31 -0
- data/lib/spiderfw/model/mixins/converted.rb +110 -0
- data/lib/spiderfw/model/mixins/list.rb +135 -0
- data/lib/spiderfw/model/mixins/mixins.rb +7 -0
- data/lib/spiderfw/model/mixins/state_machine.rb +81 -0
- data/lib/spiderfw/model/mixins/synchronized.rb +88 -0
- data/lib/spiderfw/model/mixins/tree.rb +296 -0
- data/lib/spiderfw/model/mixins/versioned.rb +147 -0
- data/lib/spiderfw/model/model.rb +226 -0
- data/lib/spiderfw/model/model_hash.rb +50 -0
- data/lib/spiderfw/model/proxy_model.rb +37 -0
- data/lib/spiderfw/model/query.rb +147 -0
- data/lib/spiderfw/model/query_funcs.rb +99 -0
- data/lib/spiderfw/model/query_set.rb +700 -0
- data/lib/spiderfw/model/request.rb +80 -0
- data/lib/spiderfw/model/storage/base_storage.rb +110 -0
- data/lib/spiderfw/model/storage/db/adapters/mssql.rb +129 -0
- data/lib/spiderfw/model/storage/db/adapters/mysql.rb +436 -0
- data/lib/spiderfw/model/storage/db/adapters/oci8.rb +533 -0
- data/lib/spiderfw/model/storage/db/adapters/sqlite.rb +163 -0
- data/lib/spiderfw/model/storage/db/connectors/odbc.rb +213 -0
- data/lib/spiderfw/model/storage/db/db.rb +13 -0
- data/lib/spiderfw/model/storage/db/db_connection_pool.rb +126 -0
- data/lib/spiderfw/model/storage/db/db_connector.rb +9 -0
- data/lib/spiderfw/model/storage/db/db_schema.rb +233 -0
- data/lib/spiderfw/model/storage/db/db_storage.rb +872 -0
- data/lib/spiderfw/model/storage/db/dialects/no_total_rows.rb +18 -0
- data/lib/spiderfw/model/storage/db/reflector.rb +36 -0
- data/lib/spiderfw/model/storage/null_storage.rb +18 -0
- data/lib/spiderfw/model/storage/schema.rb +19 -0
- data/lib/spiderfw/model/storage.rb +72 -0
- data/lib/spiderfw/model/sync.rb +94 -0
- data/lib/spiderfw/model/type.rb +13 -0
- data/lib/spiderfw/model/unit_of_work.rb +75 -0
- data/lib/spiderfw/requires.rb +21 -0
- data/lib/spiderfw/resource.rb +18 -0
- data/lib/spiderfw/setup/setup_task.rb +44 -0
- data/lib/spiderfw/tag/tag.rb +28 -0
- data/lib/spiderfw/templates/blocks/attr_if.rb +32 -0
- data/lib/spiderfw/templates/blocks/comment.rb +38 -0
- data/lib/spiderfw/templates/blocks/debugger.rb +16 -0
- data/lib/spiderfw/templates/blocks/each.rb +51 -0
- data/lib/spiderfw/templates/blocks/html.rb +70 -0
- data/lib/spiderfw/templates/blocks/if.rb +35 -0
- data/lib/spiderfw/templates/blocks/pass.rb +16 -0
- data/lib/spiderfw/templates/blocks/render.rb +23 -0
- data/lib/spiderfw/templates/blocks/run.rb +30 -0
- data/lib/spiderfw/templates/blocks/tag.rb +21 -0
- data/lib/spiderfw/templates/blocks/tag_if.rb +29 -0
- data/lib/spiderfw/templates/blocks/text.rb +43 -0
- data/lib/spiderfw/templates/blocks/widget.rb +58 -0
- data/lib/spiderfw/templates/blocks/yield.rb +18 -0
- data/lib/spiderfw/templates/layout.rb +44 -0
- data/lib/spiderfw/templates/resources/less.rb +14 -0
- data/lib/spiderfw/templates/template.rb +625 -0
- data/lib/spiderfw/templates/template_blocks.rb +185 -0
- data/lib/spiderfw/utils/annotations.rb +201 -0
- data/lib/spiderfw/utils/fork.rb +75 -0
- data/lib/spiderfw/utils/hash_comparison.rb +8 -0
- data/lib/spiderfw/utils/inflector.rb +42 -0
- data/lib/spiderfw/utils/logger.rb +92 -0
- data/lib/spiderfw/utils/monkey/class.rb +59 -0
- data/lib/spiderfw/utils/monkey/date_time.rb +34 -0
- data/lib/spiderfw/utils/monkey/debugger.rb +25 -0
- data/lib/spiderfw/utils/monkey/exception.rb +23 -0
- data/lib/spiderfw/utils/monkey/kernel.rb +12 -0
- data/lib/spiderfw/utils/monkey/module.rb +12 -0
- data/lib/spiderfw/utils/monkey/symbol.rb +7 -0
- data/lib/spiderfw/utils/monkey/time.rb +17 -0
- data/lib/spiderfw/utils/multi_level_hash.rb +33 -0
- data/lib/spiderfw/utils/periodic_runner.rb +50 -0
- data/lib/spiderfw/utils/rails.rb +78 -0
- data/lib/spiderfw/utils/rails_app.rb +53 -0
- data/lib/spiderfw/utils/sanitizer.rb +178 -0
- data/lib/spiderfw/utils/setup_task.rb +35 -0
- data/lib/spiderfw/utils/shared_store/file_shared_store.rb +73 -0
- data/lib/spiderfw/utils/shared_store/memory_shared_store.rb +46 -0
- data/lib/spiderfw/utils/shared_store.rb +47 -0
- data/lib/spiderfw/utils/test_case.rb +24 -0
- data/lib/spiderfw/utils/thread_out.rb +24 -0
- data/lib/spiderfw/version.rb +3 -0
- data/lib/spiderfw/widget/rest_model.rb +20 -0
- data/lib/spiderfw/widget/widget.rb +671 -0
- data/lib/spiderfw/widget/widget_attributes.rb +54 -0
- data/lib/spiderfw/widget/widget_plugin.rb +37 -0
- data/lib/spiderfw.rb +509 -0
- data/spider.gemspec +43 -0
- data/views/errors/404.shtml +6 -0
- data/views/errors/error.layout.shtml +70 -0
- data/views/errors/error_generic.shtml +6 -0
- metadata +800 -0
|
@@ -0,0 +1,2068 @@
|
|
|
1
|
+
require 'spiderfw/model/mixins/state_machine'
|
|
2
|
+
require 'spiderfw/model/element'
|
|
3
|
+
require 'iconv'
|
|
4
|
+
|
|
5
|
+
module Spider; module Model
|
|
6
|
+
|
|
7
|
+
# The main class for interacting with data.
|
|
8
|
+
# When not dealing with legacy storages, subclasses should use Managed instead, which provides an id and
|
|
9
|
+
# other conveniences.
|
|
10
|
+
#
|
|
11
|
+
# Each BaseModel subclass defines a model; instances can be used as "data objects":
|
|
12
|
+
# they will interact with the Mapper loading and saving the values associated with the BaseModel's defined elements.
|
|
13
|
+
#
|
|
14
|
+
# Each element defines an instance variable, a getter and a setter. If the instance is set to #autoload,
|
|
15
|
+
# when a getter is first called the mapper will fetch the value from the Storage .
|
|
16
|
+
#
|
|
17
|
+
# Elements can be of one of the base types (Spider::Model.base_types), of a DataType, or other models. In the last
|
|
18
|
+
# case, they define a relationship between models.
|
|
19
|
+
#
|
|
20
|
+
# Basic usage:
|
|
21
|
+
# model Food < BaseModel
|
|
22
|
+
# element :name, String
|
|
23
|
+
# end
|
|
24
|
+
# model Animal < BaseModel
|
|
25
|
+
# element :name, String
|
|
26
|
+
# many :friends, Animal
|
|
27
|
+
# choice :favorite_food, Food
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# salmon = Food.new(:name => 'Salmon')
|
|
31
|
+
# salmon.save
|
|
32
|
+
# cat = Animal.new(:name => 'Cat', :favorite_food = salmon)
|
|
33
|
+
# weasel = Animal.new(:name => 'Weasel', :friends => [cat])
|
|
34
|
+
# weasel.save
|
|
35
|
+
# cat.friends << weasel
|
|
36
|
+
# cat.save
|
|
37
|
+
#
|
|
38
|
+
# wizzy = Animal.load(:name => 'Weasel')
|
|
39
|
+
# p wizzy.friends
|
|
40
|
+
# => 'Cat'
|
|
41
|
+
# p wizzy.friends[0].favorite_food
|
|
42
|
+
# => 'Salmon'
|
|
43
|
+
#
|
|
44
|
+
# bear = Animal.new(:name => 'Bear', :favorite_food = salmon)
|
|
45
|
+
# bear.save
|
|
46
|
+
# salmon_lovers = Animal.where{ favorite_food == salmon }
|
|
47
|
+
# p salmon_lovers.length
|
|
48
|
+
# => 2
|
|
49
|
+
# p salmon_lovers[0].name
|
|
50
|
+
# => 'Cat'
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BaseModel
|
|
54
|
+
include Spider::Logger
|
|
55
|
+
include DataTypes
|
|
56
|
+
include StateMachine
|
|
57
|
+
|
|
58
|
+
# The BaseModel class itself. Used when dealing with proxy objects.
|
|
59
|
+
attr_reader :model
|
|
60
|
+
# An Hash of loaded elements
|
|
61
|
+
attr_reader :loaded_elements
|
|
62
|
+
# Model instance or QuerySet containing the object
|
|
63
|
+
attr_accessor :_parent
|
|
64
|
+
# If _parent is a model instance, which element points to this one
|
|
65
|
+
attr_accessor :_parent_element
|
|
66
|
+
|
|
67
|
+
class <<self
|
|
68
|
+
# An Hash of model attributes. They can be used freely.
|
|
69
|
+
attr_reader :attributes
|
|
70
|
+
# An array of element names, in definition order.
|
|
71
|
+
attr_reader :elements_order
|
|
72
|
+
# An Hash of integrated models => corresponding integrated element name.
|
|
73
|
+
attr_reader :integrated_models
|
|
74
|
+
# An Hash of polymorphic models => polymorphic params
|
|
75
|
+
attr_reader :polymorphic_models
|
|
76
|
+
# An Array of named sequences.
|
|
77
|
+
attr_reader :sequences
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Copies this class' elements to the subclass.
|
|
83
|
+
def self.inherited(subclass) #:nodoc:
|
|
84
|
+
# FIXME: might need to clone every element
|
|
85
|
+
@subclasses ||= []
|
|
86
|
+
@subclasses << subclass
|
|
87
|
+
each_element do |el|
|
|
88
|
+
subclass.add_element(el.clone)
|
|
89
|
+
end
|
|
90
|
+
subclass.instance_variable_set("@mapper_procs_subclass", @mapper_procs_subclass.clone) if @mapper_procs_subclass
|
|
91
|
+
subclass.instance_variable_set("@mapper_modules", @mapper_modules.clone) if @mapper_modules
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns the parent Spider::App of the module
|
|
95
|
+
def self.app
|
|
96
|
+
return @app if @app
|
|
97
|
+
app = self
|
|
98
|
+
while (!app.include?(Spider::App))
|
|
99
|
+
app = app.parent_module
|
|
100
|
+
end
|
|
101
|
+
@app = app
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns an instance of the Model with #autoload set to false
|
|
105
|
+
def self.static(value=nil)
|
|
106
|
+
obj = self.new(value)
|
|
107
|
+
obj.autoload = false
|
|
108
|
+
return obj
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#######################################
|
|
112
|
+
# Model definition methods #
|
|
113
|
+
#######################################
|
|
114
|
+
|
|
115
|
+
# Defines an element.
|
|
116
|
+
# Arguments are element name (a Symbol), element type, and a Hash of attributes.
|
|
117
|
+
#
|
|
118
|
+
# Type may be a Class: a base type (see Spider::Model.base_types), a DataType subclass,
|
|
119
|
+
# or a BaseModel subclass; or an Array or a Hash, in which case an InlineModel will be created.
|
|
120
|
+
#
|
|
121
|
+
# An Element instance will be available in Model::BaseModel.elements; getter and setter methods will be defined on
|
|
122
|
+
# the class.
|
|
123
|
+
#
|
|
124
|
+
# If a block is passed to this method, type will be 'extended': a custom junction association will be created,
|
|
125
|
+
# effectively adding elements to the type only in this model's context.
|
|
126
|
+
# Example:
|
|
127
|
+
# class Animal < BaseModel
|
|
128
|
+
# element :name, String
|
|
129
|
+
# element :friends, Animal, :multiple => true do
|
|
130
|
+
# element :how_much, String
|
|
131
|
+
# end
|
|
132
|
+
# end
|
|
133
|
+
# cat = Animal.new(:name => 'Cat')
|
|
134
|
+
# dog = Animal.new(:name => 'Dog')
|
|
135
|
+
# cat.friends << dog
|
|
136
|
+
# cat.friend[0].how_much = 'Not very much'
|
|
137
|
+
#
|
|
138
|
+
# Returns the created Element.
|
|
139
|
+
#
|
|
140
|
+
# Some used attributes:
|
|
141
|
+
# :primary_key:: (bool) The element is a primary key
|
|
142
|
+
# :length:: (number) Maximum length of the element (if meaningful)
|
|
143
|
+
# :required:: (bool) The element must always have a value
|
|
144
|
+
# :multiple:: (bool) defines a 1|n -> n relationship
|
|
145
|
+
# :label:: (string) a short description, used by the UI
|
|
146
|
+
# :association:: (symbol) A named association (such as :choice, :multiple_choice, etc.)
|
|
147
|
+
# :lazy:: (bool, array or symbol) If true, the element will be placed in the :default lazy group;
|
|
148
|
+
# if a symbol or an array of symbols is passed, the element will be placed in those groups.
|
|
149
|
+
# (see Element#lazy_groups)
|
|
150
|
+
# :reverse:: (symbol) The reverse element in the relationship to the other model
|
|
151
|
+
# :add_reverse:: (symbol) Adds an element on the other model, and sets it as the association reverse.
|
|
152
|
+
# :add_multiple_reverse:: (symbol) Adds a multiple element on the other model, and sets it as the association reverse.
|
|
153
|
+
# :element_position:: (number) inserts the element at the specified position in the elements order
|
|
154
|
+
# :auto:: (bool) Informative: the value is set automatically through some mechanism
|
|
155
|
+
# :autoincrement:: (bool) The value (which must be a Fixnum) will be autoincremented by the mapper
|
|
156
|
+
# :integrate:: (bool or symbol) type's elements will be available to this class
|
|
157
|
+
# as if they were defined here (see #integrate)
|
|
158
|
+
# :integrated_from:: (symbol) the name of the element from which this element is integrated
|
|
159
|
+
# :integrated_from_element:: (symbol) the name of the element of the child object from which this element is integrated
|
|
160
|
+
# :hidden:: (bool) a hint that the element shouldn't be shown by the UI
|
|
161
|
+
# :computed_from:: (array of symbols) the element is not mapped; its value is computed
|
|
162
|
+
# by the class from the given elements.
|
|
163
|
+
# :unmapped:: (bool) the element is not mapped.
|
|
164
|
+
# :check:: (a Proc, or a Regexp, or a Hash of messages => Regexp|Proc). See #check
|
|
165
|
+
# :through:: (a BaseModel subclass) model representing the many to many relationship.
|
|
166
|
+
# :read_only:: (bool) hint to the UI that the element should not be user modifiable.
|
|
167
|
+
# :owned:: (bool) only this model holds references to type
|
|
168
|
+
# :condition:: (hash or Condition) Restricts an association always adding the condition.
|
|
169
|
+
#
|
|
170
|
+
# Other attributes may be used by DataTypes (see #DataType::ClassMethods.take_attributes), and other code.
|
|
171
|
+
# See also Element.
|
|
172
|
+
def self.element(name, type, attributes={}, &proc)
|
|
173
|
+
name = name.to_sym
|
|
174
|
+
@elements ||= {}
|
|
175
|
+
@elements_order ||= []
|
|
176
|
+
if type.class == Class
|
|
177
|
+
default_attributes = case type.name
|
|
178
|
+
when 'String'
|
|
179
|
+
{:length => 255}
|
|
180
|
+
else
|
|
181
|
+
{}
|
|
182
|
+
end
|
|
183
|
+
else
|
|
184
|
+
default_attributes = {}
|
|
185
|
+
end
|
|
186
|
+
attributes = default_attributes.merge(attributes)
|
|
187
|
+
# if (type.class == Class && Model.base_type(type))
|
|
188
|
+
# type = Model.base_type(type)
|
|
189
|
+
# els
|
|
190
|
+
if (type.class == Hash)
|
|
191
|
+
type = create_inline_model(name, type)
|
|
192
|
+
attributes[:inline] = true
|
|
193
|
+
end
|
|
194
|
+
if (attributes[:integrated_from])
|
|
195
|
+
if (attributes[:integrated_from].class == String)
|
|
196
|
+
parts = attributes[:integrated_from].split('.')
|
|
197
|
+
attributes[:integrated_from] = @elements[parts[0].to_sym]
|
|
198
|
+
attributes[:integrated_from_element] = parts[1].to_sym if parts[1]
|
|
199
|
+
end
|
|
200
|
+
if (!attributes[:integrated_from_element])
|
|
201
|
+
attributes[:integrated_from_element] = name
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
if (attributes[:condition] && !attributes[:condition].is_a?(Condition))
|
|
205
|
+
attributes[:condition] = Condition.new(attributes[:condition])
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
orig_type = type
|
|
210
|
+
assoc_type = nil
|
|
211
|
+
if (attributes[:junction] || (attributes[:multiple] && (!attributes[:add_reverse]) && (!attributes[:has_single_reverse]) && \
|
|
212
|
+
# FIXME! the first check is needed when the referenced class has not been parsed yet
|
|
213
|
+
# but now it assumes that the reverse is not multiple if it is not defined
|
|
214
|
+
(attributes[:has_single_reverse] == false || !attributes[:reverse] || (!type.elements[attributes[:reverse]] || type.elements[attributes[:reverse]].multiple?))))
|
|
215
|
+
attributes[:anonymous_model] = true
|
|
216
|
+
attributes[:owned] = true unless attributes[:owned] != nil
|
|
217
|
+
first_model = self.first_definer(name)
|
|
218
|
+
assoc_type_name = Spider::Inflector.camelize(name)
|
|
219
|
+
create_junction = true
|
|
220
|
+
if (attributes[:through])
|
|
221
|
+
assoc_type = attributes[:through]
|
|
222
|
+
create_junction = false
|
|
223
|
+
elsif (first_model.const_defined?(assoc_type_name) )
|
|
224
|
+
assoc_type = first_model.const_get(assoc_type_name)
|
|
225
|
+
if (!assoc_type.attributes[:sub_model]) # other kind of inline model
|
|
226
|
+
assoc_type_name += 'Junction'
|
|
227
|
+
create_junction = false if (first_model.const_defined?(assoc_type_name))
|
|
228
|
+
else
|
|
229
|
+
create_junction = false
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
attributes[:junction] = true
|
|
233
|
+
attributes[:junction_id] = :id unless attributes.has_key?(:junction_id)
|
|
234
|
+
if (attributes[:junction_our_name])
|
|
235
|
+
self_name = attributes[:junction_our_name]
|
|
236
|
+
else
|
|
237
|
+
self_name = first_model.short_name.gsub('/', '_').downcase.to_sym
|
|
238
|
+
end
|
|
239
|
+
attributes[:reverse] = self_name
|
|
240
|
+
unless attributes[:junction_their_element]
|
|
241
|
+
other_name = Spider::Inflector.underscore(orig_type.short_name == self.short_name ? orig_type.name : orig_type.short_name).gsub('/', '_').downcase.to_sym
|
|
242
|
+
other_name = :"#{other_name}_ref" if (orig_type.elements[other_name])
|
|
243
|
+
attributes[:junction_their_element] = other_name
|
|
244
|
+
end
|
|
245
|
+
other_name = attributes[:junction_their_element]
|
|
246
|
+
if (create_junction)
|
|
247
|
+
assoc_type = first_model.const_set(assoc_type_name, Class.new(BaseModel))
|
|
248
|
+
assoc_type.attributes[:sub_model] = self
|
|
249
|
+
assoc_type.element(attributes[:junction_id], Fixnum, :primary_key => true, :autoincrement => true, :hidden => true)
|
|
250
|
+
assoc_type.element(self_name, self, :hidden => true, :reverse => name, :association => :choice) # FIXME: must check if reverse exists?
|
|
251
|
+
# FIXME! fix in case of clashes with existent elements
|
|
252
|
+
assoc_type.element(other_name, orig_type, :association => :choice)
|
|
253
|
+
assoc_type.integrate(other_name, :hidden => true, :no_pks => true) # FIXME: in some cases we want the integrated elements
|
|
254
|
+
if (proc) # to be hidden, but the integrated el instead
|
|
255
|
+
attributes[:extended] = true
|
|
256
|
+
attributes[:keep_junction] = true
|
|
257
|
+
assoc_type.class_eval(&proc)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
attributes[:keep_junction] = true if (attributes[:through] && attributes[:keep_junction] != false)
|
|
261
|
+
attributes[:association_type] = assoc_type
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
@elements[name] = Element.new(name, type, attributes)
|
|
265
|
+
|
|
266
|
+
if (attributes[:add_reverse] && attributes[:add_reverse].is_a?(Symbol))
|
|
267
|
+
attributes[:add_reverse] = {:name => attributes[:add_reverse]}
|
|
268
|
+
end
|
|
269
|
+
if (attributes[:add_multiple_reverse] && attributes[:add_multiple_reverse].is_a?(Symbol))
|
|
270
|
+
attributes[:add_multiple_reverse] = {:name => attributes[:add_multiple_reverse]}
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
if (attributes[:add_reverse])
|
|
274
|
+
unless (orig_type.elements[attributes[:add_reverse]])
|
|
275
|
+
attributes[:reverse] ||= attributes[:add_reverse][:name]
|
|
276
|
+
rev = attributes[:add_reverse].merge(:reverse => name, :added_reverse => true,
|
|
277
|
+
:delete_cascade => attributes[:reverse_delete_cascade])
|
|
278
|
+
rev[:through] = assoc_type if assoc_type
|
|
279
|
+
rev_name = rev.delete(:name)
|
|
280
|
+
orig_type.element(rev_name, self, rev)
|
|
281
|
+
end
|
|
282
|
+
elsif (attributes[:add_multiple_reverse])
|
|
283
|
+
unless (orig_type.elements[attributes[:add_reverse]])
|
|
284
|
+
attributes[:reverse] ||= attributes[:add_multiple_reverse][:name]
|
|
285
|
+
rev = attributes[:add_multiple_reverse].merge(:reverse => name, :multiple => true,
|
|
286
|
+
:added_reverse => true, :delete_cascade => attributes[:reverse_delete_cascade])
|
|
287
|
+
rev[:through] = assoc_type if assoc_type
|
|
288
|
+
rev_name = rev.delete(:name)
|
|
289
|
+
orig_type.element(rev_name, self, rev)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
if (attributes[:lazy] == nil)
|
|
293
|
+
# if attributes[:primary_key]
|
|
294
|
+
# attributes[:lazy] = true
|
|
295
|
+
# els
|
|
296
|
+
if (type < BaseModel && attributes[:multiple])
|
|
297
|
+
# FIXME: we can load eagerly single relations if we can do a join
|
|
298
|
+
attributes[:lazy] = true
|
|
299
|
+
else
|
|
300
|
+
attributes[:lazy_check_owner] = true if type < BaseModel
|
|
301
|
+
attributes[:lazy] = :default
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if (attributes[:element_position])
|
|
308
|
+
@elements_order.insert(attributes[:element_position], name)
|
|
309
|
+
else
|
|
310
|
+
@elements_order << name
|
|
311
|
+
end
|
|
312
|
+
@primary_keys ||= []
|
|
313
|
+
if attributes[:primary_key] && !@primary_keys.include?(@elements[name])
|
|
314
|
+
@primary_keys << @elements[name]
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# class element getter
|
|
318
|
+
unless respond_to?(name)
|
|
319
|
+
(class << self; self; end).instance_eval do
|
|
320
|
+
define_method("#{name}") do
|
|
321
|
+
@elements[name]
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
ivar = :"@#{ name }"
|
|
327
|
+
|
|
328
|
+
unless self.const_defined?(:ElementMethods)
|
|
329
|
+
em = self.const_set(:ElementMethods, Module.new)
|
|
330
|
+
include em
|
|
331
|
+
|
|
332
|
+
end
|
|
333
|
+
element_methods = self.const_get(:ElementMethods)
|
|
334
|
+
|
|
335
|
+
#instance variable getter
|
|
336
|
+
element_methods.send(:define_method, name) do
|
|
337
|
+
element = self.class.elements[name]
|
|
338
|
+
if (element.integrated?)
|
|
339
|
+
integrated = get(element.integrated_from.name)
|
|
340
|
+
return integrated.send(element.integrated_from_element) if integrated
|
|
341
|
+
return nil
|
|
342
|
+
end
|
|
343
|
+
if element_has_value?(name) || element_loaded?(name)
|
|
344
|
+
val = instance_variable_get(ivar)
|
|
345
|
+
val.set_parent(self, name) if val && element.model? && !val._parent # FIXME!!!
|
|
346
|
+
return val
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Spider.logger.debug("Element not loaded #{name} (i'm #{self.class} #{self.object_id})")
|
|
350
|
+
if autoload? && primary_keys_set?
|
|
351
|
+
if (autoload? == :save_mode)
|
|
352
|
+
mapper.load_element!(self, element)
|
|
353
|
+
else
|
|
354
|
+
mapper.load_element(self, element)
|
|
355
|
+
end
|
|
356
|
+
val = instance_variable_get(ivar)
|
|
357
|
+
prepare_value(name, val)
|
|
358
|
+
end
|
|
359
|
+
if (!val && element.model? && element.multiple?)
|
|
360
|
+
val = instance_variable_set(ivar, instantiate_element(name))
|
|
361
|
+
end
|
|
362
|
+
val.set_parent(self, name) if element.model? && val && !val._parent # FIXME!!!
|
|
363
|
+
return val
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
#instance_variable_setter
|
|
367
|
+
element_methods.send(:define_method, "#{name}=") do |val|
|
|
368
|
+
element = self.class.elements[name]
|
|
369
|
+
was_loaded = element_loaded?(element)
|
|
370
|
+
#@_autoload = false unless element.primary_key?
|
|
371
|
+
if (element.integrated?)
|
|
372
|
+
integrated_obj = get(element.integrated_from)
|
|
373
|
+
unless integrated_obj
|
|
374
|
+
integrated_obj = instantiate_element(element.integrated_from.name)
|
|
375
|
+
set(element.integrated_from, integrated_obj)
|
|
376
|
+
end
|
|
377
|
+
#integrated_obj.autoload = false
|
|
378
|
+
res = integrated_obj.send("#{element.integrated_from_element}=", val)
|
|
379
|
+
@modified_elements[name] = true unless element.primary_key?
|
|
380
|
+
return res
|
|
381
|
+
end
|
|
382
|
+
if (val && element.model?)
|
|
383
|
+
if (element.multiple?)
|
|
384
|
+
unless (val.is_a?(QuerySet))
|
|
385
|
+
qs = instantiate_element(name)
|
|
386
|
+
if (val.is_a?(Enumerable))
|
|
387
|
+
val.each do |row|
|
|
388
|
+
row = element.type.new(row) unless row.is_a?(BaseModel)
|
|
389
|
+
qs << row
|
|
390
|
+
end
|
|
391
|
+
else
|
|
392
|
+
qs << val
|
|
393
|
+
end
|
|
394
|
+
val = qs
|
|
395
|
+
end
|
|
396
|
+
else
|
|
397
|
+
val = element.model.new(val) unless val.is_a?(BaseModel)
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
val = prepare_child(element.name, val)
|
|
401
|
+
check(name, val)
|
|
402
|
+
notify_observers(name, val)
|
|
403
|
+
old_val = instance_variable_get(ivar)
|
|
404
|
+
@modified_elements[name] = true if !element.primary_key? && (!was_loaded || val != old_val)
|
|
405
|
+
instance_variable_set(ivar, val)
|
|
406
|
+
#extend_element(name)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
if (attributes[:integrate])
|
|
410
|
+
integrate_params = attributes[:integrate].is_a?(Hash) ? attributes[:integrate] : {}
|
|
411
|
+
integrate(name, integrate_params)
|
|
412
|
+
end
|
|
413
|
+
if (@subclasses)
|
|
414
|
+
@subclasses.each do |sub|
|
|
415
|
+
sub.elements[name] = @elements[name].clone
|
|
416
|
+
sub.elements_order << name
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
element_defined(@elements[name])
|
|
420
|
+
return @elements[name]
|
|
421
|
+
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def self.add_element(el)
|
|
425
|
+
@elements ||= {}
|
|
426
|
+
@elements[el.name] = el
|
|
427
|
+
@elements_order ||= []
|
|
428
|
+
@elements_order << el.name
|
|
429
|
+
@primary_keys ||= []
|
|
430
|
+
if el.attributes[:primary_key] && !@primary_keys.include?(el)
|
|
431
|
+
@primary_keys << el
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# Removes a defined element
|
|
437
|
+
def self.remove_element(el)
|
|
438
|
+
el = el.name if el.is_a?(Element)
|
|
439
|
+
@elements.delete(el)
|
|
440
|
+
@elements_order.delete(el)
|
|
441
|
+
@primary_keys.delete_if{ |pk| pk.name == el}
|
|
442
|
+
remove_method(:"#{el}") rescue NameError
|
|
443
|
+
remove_method(:"#{el}=") rescue NameError
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def self.element_defined(el)
|
|
447
|
+
if (@on_element_defined && @on_element_defined[el.name])
|
|
448
|
+
@on_element_defined[el.name].each do |proc|
|
|
449
|
+
proc.call(el)
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def self.on_element_defined(el_name, &proc)
|
|
455
|
+
@on_element_defined ||= {}
|
|
456
|
+
@on_element_defined[el_name] ||= []
|
|
457
|
+
@on_element_defined[el_name] << proc
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Integrates an element: any call to the child object's elements will be passed to the child.
|
|
461
|
+
# The element must not be multiple.
|
|
462
|
+
# Example:
|
|
463
|
+
# class Address < BaseModel
|
|
464
|
+
# element :street, String
|
|
465
|
+
# element :area_code, String
|
|
466
|
+
# end
|
|
467
|
+
# class Person < BaseModel
|
|
468
|
+
# element :name, String
|
|
469
|
+
# element :address, Address
|
|
470
|
+
# integrate :address
|
|
471
|
+
# end
|
|
472
|
+
# p = Person.new(...)
|
|
473
|
+
# p.street == p.address.street
|
|
474
|
+
def self.integrate(element_name, params={})
|
|
475
|
+
params ||= {}
|
|
476
|
+
elements[element_name].attributes[:integrated_model] = true
|
|
477
|
+
model = elements[element_name].model
|
|
478
|
+
self.attributes[:integrated_models] ||= {}
|
|
479
|
+
self.attributes[:integrated_models][model] = element_name
|
|
480
|
+
params[:except] ||= []
|
|
481
|
+
model.each_element do |el|
|
|
482
|
+
next if params[:except].include?(el.name)
|
|
483
|
+
next if elements[el.name] unless params[:overwrite] # don't overwrite existing elements
|
|
484
|
+
attributes = el.attributes.clone.merge({
|
|
485
|
+
:integrated_from => elements[element_name],
|
|
486
|
+
:integrated_from_element => el.name
|
|
487
|
+
})
|
|
488
|
+
attributes[:hidden] = params[:hidden] unless (params[:hidden].nil?)
|
|
489
|
+
if (add_rev = attributes[:add_reverse] || attributes[:add_multiple_reverse])
|
|
490
|
+
attributes[:reverse] = add_rev[:name]
|
|
491
|
+
attributes.delete(:add_reverse)
|
|
492
|
+
attributes.delete(:add_multiple_reverse)
|
|
493
|
+
end
|
|
494
|
+
attributes.delete(:primary_key) unless (params[:keep_pks])
|
|
495
|
+
name = params[:mapping] && params[:mapping][el.name] ? params[:mapping][el.name] : el.name
|
|
496
|
+
element(name, el.type, attributes)
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# Sets additional attributes on the element
|
|
501
|
+
#
|
|
502
|
+
# _Warning:_ for attributes which are parsed by the BaseModel during element definition,
|
|
503
|
+
# this will not have the desired effect; remove and redefine the element instead.
|
|
504
|
+
def self.element_attributes(element_name, attributes)
|
|
505
|
+
elements[element_name].attributes.merge!(attributes)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# Defines a multiple element. Equivalent to calling
|
|
509
|
+
# element(name, type, :multiple => true, :association => :many, ...)
|
|
510
|
+
def self.many(name, type, attributes={}, &proc)
|
|
511
|
+
attributes[:multiple] = true
|
|
512
|
+
attributes[:association] ||= :many
|
|
513
|
+
element(name, type, attributes, &proc)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Defines an element with choice association. Shorthand for
|
|
517
|
+
# element(name, type, :association => :choice, ...)
|
|
518
|
+
def self.choice(name, type, attributes={}, &proc)
|
|
519
|
+
attributes[:association] = :choice
|
|
520
|
+
element(name, type, attributes, &proc)
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# Defines a multiple element with :multiple_choice association. Shorthand for
|
|
524
|
+
# many(name, type, :association => :multiple_choice, ...)
|
|
525
|
+
def self.multiple_choice(name, type, attributes={}, &proc)
|
|
526
|
+
attributes[:association] = :multiple_choice
|
|
527
|
+
many(name, type, attributes, &proc)
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def self.element_query(name, element_name, attributes={})
|
|
531
|
+
orig_element = self.elements[element_name]
|
|
532
|
+
set_el_query = lambda do
|
|
533
|
+
orig_element = self.elements[element_name]
|
|
534
|
+
attributes = attributes.merge(orig_element.attributes)
|
|
535
|
+
attributes[:unmapped] = true
|
|
536
|
+
attributes[:element_query] = element_name
|
|
537
|
+
attributes[:association] = :element_query
|
|
538
|
+
attributes[:lazy] = true
|
|
539
|
+
attributes.delete(:add_reverse)
|
|
540
|
+
attributes.delete(:add_multiple_reverse)
|
|
541
|
+
if (orig_element.attributes[:condition])
|
|
542
|
+
cond = orig_element.attributes[:condition].clone
|
|
543
|
+
cond = cond.and(attributes[:condition]) if attributes[:condition]
|
|
544
|
+
attributes[:condition] = cond
|
|
545
|
+
end
|
|
546
|
+
element(name, orig_element.type, attributes)
|
|
547
|
+
end
|
|
548
|
+
if (orig_element)
|
|
549
|
+
set_el_query.call
|
|
550
|
+
else
|
|
551
|
+
on_element_defined(element_name, &set_el_query)
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
# Saves the element definition and evals it when first needed, avoiding problems with classes not
|
|
557
|
+
# available yet when the model is defined.
|
|
558
|
+
# FIXME: remove?
|
|
559
|
+
def self.define_elements(&proc) #:nodoc:
|
|
560
|
+
@elements_definition = proc
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# Creates an inline model
|
|
564
|
+
def self.create_inline_model(name, hash) #:nodoc:
|
|
565
|
+
model = self.const_set(Spider::Inflector.camelize(name), Class.new(InlineModel))
|
|
566
|
+
model.instance_eval do
|
|
567
|
+
hash.each do |key, val|
|
|
568
|
+
element(:id, key.class, :primary_key => true)
|
|
569
|
+
if (val.class == Hash)
|
|
570
|
+
# TODO: allow passing of multiple values like {:element1 => 'el1', :element2 => 'el2'}
|
|
571
|
+
else
|
|
572
|
+
element(:desc, val.class)
|
|
573
|
+
end
|
|
574
|
+
break
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
model.data = hash
|
|
578
|
+
return model
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# An array of other models this class points to.
|
|
582
|
+
def self.submodels
|
|
583
|
+
elements.select{ |name, el| el.model? }.map{ |name, el| el.model }
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def self.extend_model(model, params={}) #:nodoc:
|
|
588
|
+
if (model == superclass) # first undo table per class inheritance
|
|
589
|
+
@elements = {}
|
|
590
|
+
@elements_order = []
|
|
591
|
+
end
|
|
592
|
+
primary_keys.each{ |k| remove_element(k) } if (params[:replace_pks])
|
|
593
|
+
model.primary_keys.each{ |k| remove_element(k) }
|
|
594
|
+
integrated_name = params[:name]
|
|
595
|
+
if (!integrated_name)
|
|
596
|
+
integrated_name = (self.parent_module == model.parent_module) ? model.short_name : model.name
|
|
597
|
+
integrated_name = Spider::Inflector.underscore(integrated_name).gsub('/', '_')
|
|
598
|
+
end
|
|
599
|
+
integrated_name = integrated_name.to_sym
|
|
600
|
+
@extended_models ||= {}
|
|
601
|
+
@extended_models[model] = integrated_name
|
|
602
|
+
attributes = {}
|
|
603
|
+
attributes[:hidden] = true unless (params[:hide_integrated] == false)
|
|
604
|
+
attributes[:delete_cascade] = params[:delete_cascade]
|
|
605
|
+
integrated = element(integrated_name, model, attributes)
|
|
606
|
+
integrate_options = {:keep_pks => true}.merge((params[:integrate_options] || {}))
|
|
607
|
+
integrate(integrated_name, integrate_options)
|
|
608
|
+
model.elements_array.select{ |el| el.attributes[:local_pk] }.each{ |el| remove_element(el.name) }
|
|
609
|
+
|
|
610
|
+
unless (params[:no_local_pk] || !elements_array.select{ |el| el.attributes[:local_pk] }.empty?)
|
|
611
|
+
# FIXME: check if :id is already defined
|
|
612
|
+
pk_name = @elements[:id] ? :"id_#{self.short_name.downcase}" : :id
|
|
613
|
+
element(pk_name, Fixnum, :autoincrement => true, :local_pk => true, :hidden => true)
|
|
614
|
+
end
|
|
615
|
+
if (params[:add_polymorphic])
|
|
616
|
+
model.polymorphic(self, :through => integrated_name)
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# Externalizes the superclass elements making the superclass an external integrated element.
|
|
621
|
+
# Parameters may be:
|
|
622
|
+
# * :name (symbol) name of the created element
|
|
623
|
+
# * :delete_cascade (bool) delete cascade the superclass instance
|
|
624
|
+
# * :no_local_pk (bool) do not define an id for this class
|
|
625
|
+
# * :add_polymorphic (bool) notify the superclass that it is extended, making polymorphic queries possible
|
|
626
|
+
def self.class_table_inheritance(params={})
|
|
627
|
+
self.extend_model(superclass, params)
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
# Makes the class use the superclass storage
|
|
631
|
+
def self.inherit_storage
|
|
632
|
+
self.attributes[:inherit_storage] = true
|
|
633
|
+
(class << self; self; end).instance_eval do
|
|
634
|
+
define_method(:storage) do
|
|
635
|
+
superclass.storage
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# Sets a fixed condition.
|
|
641
|
+
def self.condition(condition)
|
|
642
|
+
self.attributes[:condition] = condition
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
#
|
|
646
|
+
#--
|
|
647
|
+
# TODO: document me
|
|
648
|
+
def self.group(name, &proc) #:nodoc:
|
|
649
|
+
proxy = Class.new(ProxyModel).proxy(name.to_s+'_', self)
|
|
650
|
+
proxy.instance_eval(&proc)
|
|
651
|
+
proxy.each_element do |el|
|
|
652
|
+
element(name.to_s+'_'+el.name.to_s, el.type, el.attributes.clone)
|
|
653
|
+
end
|
|
654
|
+
define_method(name) do
|
|
655
|
+
@proxies ||= {}
|
|
656
|
+
return @proxies[name] ||= proxy.new
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
# Add a subclass, allowing polymorphic queries on it.
|
|
662
|
+
def self.polymorphic(model, options)
|
|
663
|
+
through = options[:through] || Spider::Inflector.underscore(self.name).gsub('/', '_')
|
|
664
|
+
through = through.to_sym
|
|
665
|
+
@polymorphic_models ||= {}
|
|
666
|
+
@polymorphic_models[model] = {:through => through}
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
# Sets or gets class attributes (a Hash).
|
|
670
|
+
# If given a hash of attributes, will merge them with class attributes.
|
|
671
|
+
# Model attributes are generally empty, and can be used by apps.
|
|
672
|
+
def self.attributes(val=nil)
|
|
673
|
+
@attributes ||= {}
|
|
674
|
+
if (val)
|
|
675
|
+
@attributes.merge!(val)
|
|
676
|
+
end
|
|
677
|
+
@attributes
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
# Sets a model attribute. See #self.attributes
|
|
681
|
+
def self.attribute(name, value)
|
|
682
|
+
@attributes ||= {}
|
|
683
|
+
@attributes[name] = value
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
# Adds a sequence to the model.
|
|
687
|
+
def self.sequence(name)
|
|
688
|
+
@sequences ||= []
|
|
689
|
+
@sequences << name
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
# Model sequences.
|
|
693
|
+
def self.sequences
|
|
694
|
+
@sequences ||= []
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# Does nothing. This method is to keep note of elements created in other models.
|
|
698
|
+
def self._added_elements(&proc)
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
#####################################################
|
|
702
|
+
# Methods returning information about the model #
|
|
703
|
+
#####################################################
|
|
704
|
+
|
|
705
|
+
# Underscored local name (without namespaces)
|
|
706
|
+
def self.short_name
|
|
707
|
+
return Inflector.underscore(self.name.match(/([^:]+)$/)[1])
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# False for BaseModel (true for Spider::Model::Managed).
|
|
711
|
+
def self.managed?
|
|
712
|
+
return false
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
# Name
|
|
716
|
+
def self.to_s
|
|
717
|
+
self.name
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# Sets the singolar and/or the plural label for the model
|
|
721
|
+
# Returns the singlular label
|
|
722
|
+
def self.label(sing=nil, plur=nil)
|
|
723
|
+
@label = sing if sing
|
|
724
|
+
@label_plural = plur if plur
|
|
725
|
+
@label || self.name || ''
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
# Sets/retrieves the plural form for the label
|
|
729
|
+
def self.label_plural(val=nil)
|
|
730
|
+
@label_plural = val if (val)
|
|
731
|
+
@label_plural || self.name || ''
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
########################################################
|
|
735
|
+
# Methods returning information about the elements #
|
|
736
|
+
########################################################
|
|
737
|
+
|
|
738
|
+
# An Hash of Elements, indexed by name.
|
|
739
|
+
def self.elements
|
|
740
|
+
@elements
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
# An array of the model's Elements.
|
|
744
|
+
def self.elements_array
|
|
745
|
+
@elements_order.map{ |key| @elements[key] }
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
# Yields each element in order.
|
|
749
|
+
def self.each_element
|
|
750
|
+
return unless @elements_order
|
|
751
|
+
@elements_order.each do |name|
|
|
752
|
+
yield elements[name]
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
# Returns true if the model has given element name.
|
|
757
|
+
def self.has_element?(name)
|
|
758
|
+
return elements[name] ? true : false
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
# An array of elements with primary_key attribute set.
|
|
762
|
+
def self.primary_keys
|
|
763
|
+
@primary_keys
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# Returns the model actually defining element_name; that could be the model
|
|
767
|
+
# itself, a superclass, or an integrated model.
|
|
768
|
+
def self.first_definer(element_name)
|
|
769
|
+
if (self.superclass.elements && self.superclass.elements[element_name])
|
|
770
|
+
return self.superclass.first_definer(element_name)
|
|
771
|
+
end
|
|
772
|
+
if (self.attributes[:integrated_models])
|
|
773
|
+
self.attributes[:integrated_models].keys.each do |mod|
|
|
774
|
+
return mod.first_definer(element_name) if (mod.elements[element_name])
|
|
775
|
+
end
|
|
776
|
+
end
|
|
777
|
+
return self
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
# Returns true if the element with given name is associated with the passed
|
|
781
|
+
# association.
|
|
782
|
+
# This method should be used instead of querying the element's association directly,
|
|
783
|
+
# since subclasses and mixins may extend this method to provide association equivalence.
|
|
784
|
+
def self.element_association?(element_name, association)
|
|
785
|
+
return true if elements[element_name].association = association
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
# An Hash of extended models => element name of the extended model element
|
|
789
|
+
def self.extended_models
|
|
790
|
+
@extended_models ||= {}
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
##############################################################
|
|
794
|
+
# Storage, mapper and loading (Class methods) #
|
|
795
|
+
##############################################################
|
|
796
|
+
|
|
797
|
+
# The given module will be mixed in any mapper used by the class.
|
|
798
|
+
def self.mapper_include(mod)
|
|
799
|
+
@mapper_modules ||= []
|
|
800
|
+
@mapper_modules << mod
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
# The given proc will be mixed in the mapper used by this class
|
|
804
|
+
# Note that the proc will be converted to a Module, so any overridden methods will still have
|
|
805
|
+
# access to the super method.
|
|
806
|
+
def self.with_mapper(*params, &proc)
|
|
807
|
+
# @mapper_procs ||= []
|
|
808
|
+
# @mapper_procs << proc
|
|
809
|
+
mod = Module.new(&proc)
|
|
810
|
+
mapper_include(mod)
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
# FIXME: remove
|
|
814
|
+
def self.with_mapper_subclasses(*params, &proc) #:nodoc:
|
|
815
|
+
@mapper_procs_subclass ||= []
|
|
816
|
+
@mapper_procs_subclass << proc
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
# Like #with_mapper, but will mixin the block only if the mapper matches params.
|
|
820
|
+
# Possible params are:
|
|
821
|
+
# - a String, matching the class' use_storage
|
|
822
|
+
def self.with_mapper_for(*params, &proc)
|
|
823
|
+
@mapper_procs_for ||= []
|
|
824
|
+
@mapper_procs_for << [params, proc]
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
# Sets the url or the name of the storage to use
|
|
828
|
+
def self.use_storage(name=nil)
|
|
829
|
+
@use_storage = name if name
|
|
830
|
+
@use_storage
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
# Returns the current default storage for the class
|
|
834
|
+
# The storage to use can be set with #use_storage
|
|
835
|
+
def self.storage
|
|
836
|
+
return @storage if @storage
|
|
837
|
+
if (!@use_storage && self.attributes[:sub_model])
|
|
838
|
+
@use_storage = self.attributes[:sub_model].use_storage
|
|
839
|
+
end
|
|
840
|
+
return @use_storage ? get_storage(@use_storage) : get_storage
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
# Returns an instancethe storage corresponding to the storage_string if it is given,
|
|
844
|
+
# or of the default storage otherwise.
|
|
845
|
+
# The storage string can be a storage url (see #Storage.get_storage), or a named storage
|
|
846
|
+
# defined in configuration
|
|
847
|
+
#--
|
|
848
|
+
# Mixin!
|
|
849
|
+
def self.get_storage(storage_string='default')
|
|
850
|
+
storage_regexp = /([\w\d]+?):(.+)/
|
|
851
|
+
if (storage_string !~ storage_regexp)
|
|
852
|
+
orig_string = storage_string
|
|
853
|
+
storage_conf = Spider.conf.get('storages')[storage_string]
|
|
854
|
+
storage_string = storage_conf['url'] if storage_conf
|
|
855
|
+
if (!storage_string || storage_string !~ storage_regexp)
|
|
856
|
+
raise ModelException, "No storage '#{orig_string}' found"
|
|
857
|
+
end
|
|
858
|
+
end
|
|
859
|
+
type, url = $1, $2
|
|
860
|
+
storage = Storage.get_storage(type, url)
|
|
861
|
+
storage.configure(storage_conf) if storage_conf
|
|
862
|
+
return storage
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
# Returns an instance of the default mapper for the class.
|
|
866
|
+
def self.mapper
|
|
867
|
+
@mapper ||= get_mapper(storage)
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
# Returns an instance of the mapper for the given storage
|
|
871
|
+
def self.get_mapper(storage)
|
|
872
|
+
# map_class = self.attributes[:inherit_storage] ? superclass : self
|
|
873
|
+
mapper = storage.get_mapper(self)
|
|
874
|
+
if (@mapper_modules)
|
|
875
|
+
@mapper_modules.each{ |mod| mapper.extend(mod) }
|
|
876
|
+
end
|
|
877
|
+
if (@mapper_procs)
|
|
878
|
+
@mapper_procs.each{ |proc| mapper.instance_eval(&proc) }
|
|
879
|
+
end
|
|
880
|
+
if (@mapper_procs_for)
|
|
881
|
+
@mapper_procs_for.each do |params, proc|
|
|
882
|
+
if (params.length == 1 && params[0].class == String)
|
|
883
|
+
mapper.instance_eval(&proc) if (self.use_storage == params[0])
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
end
|
|
887
|
+
if (@mapper_procs_subclass)
|
|
888
|
+
@mapper_procs_subclass.each{ |proc| mapper.instance_eval(&proc) }
|
|
889
|
+
end
|
|
890
|
+
return mapper
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
# Executes #self.where, and calls QuerySet#load on the result.
|
|
894
|
+
# Returns nil if the result is empty, the QuerySet otherwise
|
|
895
|
+
# See #self.where for parameter syntax
|
|
896
|
+
def self.find(*params, &proc)
|
|
897
|
+
qs = self.where(*params, &proc)
|
|
898
|
+
return qs.empty? ? nil : qs
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
# Executes #self.where, returning the first result.
|
|
902
|
+
# See #self.where for parameter syntax.
|
|
903
|
+
def self.load(*params, &proc)
|
|
904
|
+
return self.where(*params, &proc)[0]
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
# Returns a queryset without conditions
|
|
908
|
+
def self.all
|
|
909
|
+
return self.where
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
# Constructs a Query based on params, and returns a QuerySet
|
|
913
|
+
# Allowed parameters are:
|
|
914
|
+
# * a Query object
|
|
915
|
+
# * a Condition and an (optional) Request, or anything that can be parsed by Condition.new and Request.new
|
|
916
|
+
# If a block is provided, it is passed to Condition.parse_block.
|
|
917
|
+
# Examples:
|
|
918
|
+
# felines = Animals.where({:family => 'felines'})
|
|
919
|
+
# felines = Animals.where({:family => 'felines'}, [:name, :description])
|
|
920
|
+
# cool_animals = Animals.where{ (has_fangs == true) | (has_claws == true)}
|
|
921
|
+
# See also Condition#parse_block
|
|
922
|
+
def self.where(*params, &proc)
|
|
923
|
+
if (params[0] && params[0].is_a?(Query))
|
|
924
|
+
query = params[0]
|
|
925
|
+
qs = QuerySet.new(self, query)
|
|
926
|
+
elsif(proc)
|
|
927
|
+
qs = QuerySet.new(self)
|
|
928
|
+
qs.autoload = true
|
|
929
|
+
qs.where(&proc)
|
|
930
|
+
else
|
|
931
|
+
condition = Condition.and(params[0])
|
|
932
|
+
request = Request.new(params[1])
|
|
933
|
+
query = Query.new(condition, request)
|
|
934
|
+
qs = QuerySet.new(self, query)
|
|
935
|
+
end
|
|
936
|
+
return qs
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
# Returns the condition for a "free" text query
|
|
940
|
+
# Examples:
|
|
941
|
+
# condition = News.free_query_condition('animals')
|
|
942
|
+
# animal_news = News.where(condition)
|
|
943
|
+
def self.free_query_condition(q)
|
|
944
|
+
c = Condition.or
|
|
945
|
+
self.elements_array.each do |el|
|
|
946
|
+
if (el.type == String || el.type == Text)
|
|
947
|
+
c.set(el.name, 'ilike', '%'+q+'%')
|
|
948
|
+
end
|
|
949
|
+
end
|
|
950
|
+
return c
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
# Returns the number of objects in storage
|
|
954
|
+
def self.count(condition=nil)
|
|
955
|
+
mapper.count(condition)
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
# Can be defined to provide functionality to this model's querysets.
|
|
959
|
+
def self.extend_queryset(qs)
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
#################################################
|
|
963
|
+
# Instance methods #
|
|
964
|
+
#################################################
|
|
965
|
+
|
|
966
|
+
# The constructor may take:
|
|
967
|
+
# * an Hash of values, that will be set on the new instance; or
|
|
968
|
+
# * a BaseModel instance; its values will be set on the new instance; or
|
|
969
|
+
# * a single value; it will be set on the first primary key.
|
|
970
|
+
def initialize(values=nil)
|
|
971
|
+
@_autoload = true
|
|
972
|
+
@_has_values = false
|
|
973
|
+
@loaded_elements = {}
|
|
974
|
+
@modified_elements = {}
|
|
975
|
+
@value_observers = {}
|
|
976
|
+
@all_values_observers = []
|
|
977
|
+
@_extra = {}
|
|
978
|
+
@model = self.class
|
|
979
|
+
@all_values_observers << Proc.new do |element, new_value|
|
|
980
|
+
@_has_values = true
|
|
981
|
+
Spider::Model.unit_of_work.add(self) if (Spider::Model.unit_of_work)
|
|
982
|
+
end
|
|
983
|
+
if (values)
|
|
984
|
+
if (values.is_a? Hash)
|
|
985
|
+
values.each do |key, val|
|
|
986
|
+
set!(key, val)
|
|
987
|
+
end
|
|
988
|
+
elsif (values.is_a? BaseModel)
|
|
989
|
+
values.each_val do |name, val|
|
|
990
|
+
set(name, val) if self.class.has_element?(name)
|
|
991
|
+
end
|
|
992
|
+
elsif (values.is_a? Array)
|
|
993
|
+
self.class.primary_keys.each_index do |i|
|
|
994
|
+
set(self.class.primary_keys[i], values[i])
|
|
995
|
+
end
|
|
996
|
+
# Single unset key, single value
|
|
997
|
+
elsif ((empty_keys = self.class.primary_keys.select{ |key| !element_has_value?(key) }).length == 1)
|
|
998
|
+
set(empty_keys[0], values)
|
|
999
|
+
end
|
|
1000
|
+
end
|
|
1001
|
+
end
|
|
1002
|
+
|
|
1003
|
+
# Returns the instance's IdentityMapper
|
|
1004
|
+
def identity_mapper
|
|
1005
|
+
return Spider::Model.identity_mapper if Spider::Model.identity_mapper
|
|
1006
|
+
@identity_mapper ||= IdentityMapper.new
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
# Sets the instance's IdentityMapper.
|
|
1010
|
+
def identity_mapper=(im)
|
|
1011
|
+
@identity_mapper = im
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
# Returns a new instance for given element name.
|
|
1015
|
+
def instantiate_element(name)
|
|
1016
|
+
element = self.class.elements[name]
|
|
1017
|
+
if (element.model?)
|
|
1018
|
+
if (element.multiple?)
|
|
1019
|
+
val = QuerySet.static(element.model)
|
|
1020
|
+
else
|
|
1021
|
+
val = element.type.new
|
|
1022
|
+
val.autoload = autoload?
|
|
1023
|
+
end
|
|
1024
|
+
end
|
|
1025
|
+
return prepare_child(name, val)
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
# Prepares an object that is being set as a child.
|
|
1029
|
+
def prepare_child(name, obj)
|
|
1030
|
+
return obj if obj.nil?
|
|
1031
|
+
element = self.class.elements[name]
|
|
1032
|
+
if (element.model?)
|
|
1033
|
+
# convert between junction and real type if needed
|
|
1034
|
+
if (obj.is_a?(QuerySet) && element.attributes[:junction])
|
|
1035
|
+
obj.no_autoload do
|
|
1036
|
+
if (element.attributes[:keep_junction] && obj.model == element.type)
|
|
1037
|
+
qs = QuerySet.new(element.model)
|
|
1038
|
+
obj.each{ |el_obj|
|
|
1039
|
+
qs << {element.reverse => self, element.attributes[:junction_their_element] => el_obj}
|
|
1040
|
+
}
|
|
1041
|
+
obj = qs
|
|
1042
|
+
elsif (!element.attributes[:keep_junction] && obj.model == element.model)
|
|
1043
|
+
qs = QuerySet.new(element.type, obj.map{ |el_obj| el_obj.get(element.attributes[:junction_their_element])})
|
|
1044
|
+
obj = qs
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
end
|
|
1048
|
+
obj.identity_mapper = self.identity_mapper if obj.respond_to?(:identity_mapper)
|
|
1049
|
+
if (element.attributes[:junction] && element.attributes[:keep_junction])
|
|
1050
|
+
obj.append_element = element.attributes[:junction_their_element]
|
|
1051
|
+
end
|
|
1052
|
+
if (element.attributes[:set] && element.attributes[:set].is_a?(Hash))
|
|
1053
|
+
element.attributes[:set].each{ |k, v| obj.set(k, v) }
|
|
1054
|
+
obj.reset_modified_elements(*element.attributes[:set].keys)
|
|
1055
|
+
# FIXME: is it always ok to not set the element as modified? But otherwise sub objects
|
|
1056
|
+
# are always saved (and that's definitely no good)
|
|
1057
|
+
end
|
|
1058
|
+
else
|
|
1059
|
+
obj = prepare_value(element, obj)
|
|
1060
|
+
end
|
|
1061
|
+
return obj
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
# Returns all children that can be reached from the current path.
|
|
1065
|
+
# Path is expressed as a dotted String.
|
|
1066
|
+
def all_children(path)
|
|
1067
|
+
children = []
|
|
1068
|
+
no_autoload do
|
|
1069
|
+
el = path.shift
|
|
1070
|
+
if self.class.elements[el] && element_has_value?(el) && children = get(el)
|
|
1071
|
+
if path.length >= 1
|
|
1072
|
+
children = children.all_children(path)
|
|
1073
|
+
end
|
|
1074
|
+
end
|
|
1075
|
+
end
|
|
1076
|
+
return children
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
# Sets the object currently containing this one (BaseModel or QuerySet)
|
|
1080
|
+
def set_parent(obj, element)
|
|
1081
|
+
@_parent = obj
|
|
1082
|
+
@_parent_element = element
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
#################################################
|
|
1087
|
+
# Get and set #
|
|
1088
|
+
#################################################
|
|
1089
|
+
|
|
1090
|
+
# Returns an element.
|
|
1091
|
+
# The element may be a symbol, or a dotted path String.
|
|
1092
|
+
# Will call the associated getter.
|
|
1093
|
+
# cat.get('favorite_food.name')
|
|
1094
|
+
def get(element)
|
|
1095
|
+
element = element.name if (element.class == Spider::Model::Element)
|
|
1096
|
+
first, rest = element.to_s.split('.', 2)
|
|
1097
|
+
if (rest)
|
|
1098
|
+
return nil unless element_has_value?(first.to_sym)
|
|
1099
|
+
return send(first).get(rest)
|
|
1100
|
+
end
|
|
1101
|
+
return send(element)
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
# Returns an element without autoloading it.
|
|
1105
|
+
def get_no_load(element)
|
|
1106
|
+
res = nil
|
|
1107
|
+
no_autoload do
|
|
1108
|
+
res = get(element)
|
|
1109
|
+
end
|
|
1110
|
+
return res
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
# Sets an element.
|
|
1114
|
+
# The element can be a symbol, or a dotted path String.
|
|
1115
|
+
# Will call the associated setter.
|
|
1116
|
+
# cat.set('favorite_food.name', 'Salmon')
|
|
1117
|
+
def set(element, value, options={})
|
|
1118
|
+
element = element.name if (element.class == Element)
|
|
1119
|
+
first, rest = element.to_s.split('.', 2)
|
|
1120
|
+
if (rest)
|
|
1121
|
+
first_val = send(first)
|
|
1122
|
+
unless first_val
|
|
1123
|
+
if (options[:instantiate])
|
|
1124
|
+
first_val = instantiate_element(first.to_sym)
|
|
1125
|
+
set(first, first_val)
|
|
1126
|
+
else
|
|
1127
|
+
raise "Element #{first} is nil, can't set #{element}"
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
return first_val.set(rest, value, options)
|
|
1131
|
+
end
|
|
1132
|
+
return send("#{element}=", value)
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
# Sets an element, instantiating intermediate objects if needed
|
|
1136
|
+
def set!(element, value, options={})
|
|
1137
|
+
options[:instantiate] = true
|
|
1138
|
+
set(element, value, options)
|
|
1139
|
+
end
|
|
1140
|
+
|
|
1141
|
+
# Calls #get on element; whenever no getter responds, returns the extra data.
|
|
1142
|
+
# See #[]=
|
|
1143
|
+
def [](element)
|
|
1144
|
+
element = element.name if element.is_a?(Element)
|
|
1145
|
+
begin
|
|
1146
|
+
get(element)
|
|
1147
|
+
rescue NoMethodError
|
|
1148
|
+
return @_extra[element]
|
|
1149
|
+
end
|
|
1150
|
+
end
|
|
1151
|
+
|
|
1152
|
+
# If element is a model's element, calls #set.
|
|
1153
|
+
# Otherwise, stores the value in an "extra" hash, where it will be accessible by #[]
|
|
1154
|
+
def []=(element, value)
|
|
1155
|
+
element = element.name if element.is_a?(Element)
|
|
1156
|
+
if (self.class.elements[element])
|
|
1157
|
+
set(element, value)
|
|
1158
|
+
else
|
|
1159
|
+
@_extra[element] = value
|
|
1160
|
+
end
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
# Sets each value of a Hash.
|
|
1164
|
+
def set_hash(hash)
|
|
1165
|
+
hash.each { |key, val| set(key, val) }
|
|
1166
|
+
end
|
|
1167
|
+
|
|
1168
|
+
# Prepares a value going to be set on the object. Will convert the value to the
|
|
1169
|
+
# appropriate type.
|
|
1170
|
+
def prepare_value(element, value)
|
|
1171
|
+
element = self.class.elements[element] unless element.is_a?(Element)
|
|
1172
|
+
if (element.type < Spider::DataType)
|
|
1173
|
+
value = element.type.from_value(value) unless value.is_a?(element.type)
|
|
1174
|
+
element.type.take_attributes.each do |a|
|
|
1175
|
+
value.attributes[a] = element.attributes[a]
|
|
1176
|
+
end
|
|
1177
|
+
elsif element.model?
|
|
1178
|
+
value.autoload(autoload?, true) if value && value.respond_to?(:autolad)
|
|
1179
|
+
else
|
|
1180
|
+
case element.type.name
|
|
1181
|
+
when 'Date', 'DateTime'
|
|
1182
|
+
return nil if value.is_a?(String) && value.empty?
|
|
1183
|
+
parsed = nil
|
|
1184
|
+
if (value.is_a?(String))
|
|
1185
|
+
begin
|
|
1186
|
+
parsed = element.type.strptime(value, "%Y-%m-%dT%H:%M:%S") rescue parsed = nil
|
|
1187
|
+
parsed ||= element.type.lparse(value, :short) rescue parsed = nil
|
|
1188
|
+
parsed ||= element.type.parse(value)
|
|
1189
|
+
rescue ArgumentError => exc
|
|
1190
|
+
raise FormatError.new(element, value, _("'%s' is not a valid date"))
|
|
1191
|
+
end
|
|
1192
|
+
value = parsed
|
|
1193
|
+
end
|
|
1194
|
+
when 'String'
|
|
1195
|
+
when 'Spider::DataTypes::Text'
|
|
1196
|
+
value = value.to_s
|
|
1197
|
+
when 'Fixnum'
|
|
1198
|
+
value = value.to_i
|
|
1199
|
+
end
|
|
1200
|
+
end
|
|
1201
|
+
value
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
# Sets a value without calling the associated setter; used by the mapper.
|
|
1205
|
+
def set_loaded_value(element, value)
|
|
1206
|
+
element_name = element.is_a?(Element) ? element.name : element
|
|
1207
|
+
element = self.class.elements[element_name]
|
|
1208
|
+
if (element.integrated?)
|
|
1209
|
+
get(element.integrated_from).set_loaded_value(element.integrated_from_element, value)
|
|
1210
|
+
else
|
|
1211
|
+
value = prepare_child(element.name, value) if element.model?
|
|
1212
|
+
instance_variable_set("@#{element_name}", value)
|
|
1213
|
+
end
|
|
1214
|
+
value.loaded = true if (value.is_a?(QuerySet))
|
|
1215
|
+
element_loaded(element_name)
|
|
1216
|
+
@modified_elements[element_name] = false
|
|
1217
|
+
end
|
|
1218
|
+
|
|
1219
|
+
# Records that the element has been loaded.
|
|
1220
|
+
def element_loaded(element_name)
|
|
1221
|
+
element_name = element_name.name if (element_name.class == Element)
|
|
1222
|
+
@loaded_elements[element_name] = true
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
# Returns true if the element has been loaded by the mapper.
|
|
1226
|
+
def element_loaded?(element)
|
|
1227
|
+
element = element.name if (element.class == Element)
|
|
1228
|
+
return @loaded_elements[element]
|
|
1229
|
+
end
|
|
1230
|
+
|
|
1231
|
+
# Apply element checks for given element name and value. (See also #element, :check attribute).
|
|
1232
|
+
# Checks may be defined by the DataType, or be given as an element attribute.
|
|
1233
|
+
# The check can be a Regexp, that will be checked against the value, or a Proc, which is expected to
|
|
1234
|
+
# return true if the check is succesful, and false otherwise.
|
|
1235
|
+
# Will raise a Model::FormatError when a check is not succesful.
|
|
1236
|
+
# If the :check attribute is an Hash, the Hash keys will be used as messages, which will be passed
|
|
1237
|
+
# to the FormatError.
|
|
1238
|
+
def check(name, val)
|
|
1239
|
+
element = self.class.elements[name]
|
|
1240
|
+
element.type.check(val) if (element.type.respond_to?(:check))
|
|
1241
|
+
if (checks = element.attributes[:check])
|
|
1242
|
+
checks = {(_("'%s' is not in the correct format") % element.label) => checks} unless checks.is_a?(Hash)
|
|
1243
|
+
checks.each do |msg, check|
|
|
1244
|
+
test = case check
|
|
1245
|
+
when Regexp
|
|
1246
|
+
val == nil || val.empty? ? true : check.match(val)
|
|
1247
|
+
when Proc
|
|
1248
|
+
check.call(val)
|
|
1249
|
+
end
|
|
1250
|
+
raise FormatError.new(element, val, msg) unless test
|
|
1251
|
+
end
|
|
1252
|
+
end
|
|
1253
|
+
end
|
|
1254
|
+
|
|
1255
|
+
# Converts the object to the instance of a subclass for which this model is polymorphic.
|
|
1256
|
+
def polymorphic_become(model)
|
|
1257
|
+
raise ModelException, "#{self.class} is not polymorphic for #{model}" unless self.class.polymorphic_models[model]
|
|
1258
|
+
obj = model.new
|
|
1259
|
+
el = self.class.polymorphic_models[model][:through]
|
|
1260
|
+
obj.set(el, self)
|
|
1261
|
+
obj.element_loaded(el)
|
|
1262
|
+
return obj
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
def become(model)
|
|
1266
|
+
return self if self.class == model
|
|
1267
|
+
obj = polymorphic_become(model) rescue ModelException
|
|
1268
|
+
return obj
|
|
1269
|
+
end
|
|
1270
|
+
|
|
1271
|
+
# Converts the object to the instance of a subclass. This will instantiate the model
|
|
1272
|
+
# passed as an argument, and set each value of the current object on the new one.
|
|
1273
|
+
# No checks are made that this makes sense, so the method will fail if the "subclass" does
|
|
1274
|
+
# not contain all of the current model's elements.
|
|
1275
|
+
def subclass(model)
|
|
1276
|
+
obj = model.new
|
|
1277
|
+
elements_array.each do |el|
|
|
1278
|
+
obj.set(el, self.get(el)) if element_has_value?(el)
|
|
1279
|
+
end
|
|
1280
|
+
return obj
|
|
1281
|
+
end
|
|
1282
|
+
|
|
1283
|
+
# Returns the current autoload status
|
|
1284
|
+
def autoload?
|
|
1285
|
+
@_autoload
|
|
1286
|
+
end
|
|
1287
|
+
|
|
1288
|
+
# Enables or disables autoloading.
|
|
1289
|
+
# An autoloading object will try to load all missing elements on first access.
|
|
1290
|
+
# (see also Element#lazy_groups)
|
|
1291
|
+
def autoload=(val)
|
|
1292
|
+
autoload(val, false)
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
# Sets autoload mode
|
|
1296
|
+
# The first parameter the value of autoload to be set; it can be true, false or :save_mode (see #save_mode))
|
|
1297
|
+
# the second bool parameter specifies if the value should be propagated on all child objects.
|
|
1298
|
+
def autoload(a, traverse=true) #:nodoc:
|
|
1299
|
+
return if @_tmp_autoload_walk
|
|
1300
|
+
@_tmp_autoload_walk = true
|
|
1301
|
+
@_autoload = a
|
|
1302
|
+
if (traverse)
|
|
1303
|
+
self.class.elements_array.select{ |el| el.model? && element_has_value?(el.name)}.each do |el|
|
|
1304
|
+
val = get(el)
|
|
1305
|
+
val.autoload = a if val.respond_to?(:autoload=)
|
|
1306
|
+
end
|
|
1307
|
+
end
|
|
1308
|
+
@_tmp_autoload_walk = nil
|
|
1309
|
+
end
|
|
1310
|
+
|
|
1311
|
+
# Disables autoload.
|
|
1312
|
+
# If a block is given, the current autoload setting will be restored after yielding.
|
|
1313
|
+
def no_autoload
|
|
1314
|
+
prev_autoload = autoload?
|
|
1315
|
+
self.autoload = false
|
|
1316
|
+
if block_given?
|
|
1317
|
+
yield
|
|
1318
|
+
self.autoload = prev_autoload
|
|
1319
|
+
end
|
|
1320
|
+
return prev_autoload
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
# Sets autoload to :save_mode; elements will be autoloaded only one by one, so that
|
|
1324
|
+
# any already set data will not be overwritten
|
|
1325
|
+
# If a block is given, the current autoload setting will be restored after yielding.
|
|
1326
|
+
def save_mode
|
|
1327
|
+
prev_autoload = autoload?
|
|
1328
|
+
self.autoload = :save_mode
|
|
1329
|
+
if (block_given?)
|
|
1330
|
+
yield
|
|
1331
|
+
self.autoload = prev_autoload
|
|
1332
|
+
end
|
|
1333
|
+
return prev_autoload
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
##############################################################
|
|
1338
|
+
# Methods for getting information about element values #
|
|
1339
|
+
##############################################################
|
|
1340
|
+
|
|
1341
|
+
# Returns true if other is_a?(self.class), and has the same values for this class' primary keys.
|
|
1342
|
+
def ==(other)
|
|
1343
|
+
return false unless other
|
|
1344
|
+
return false unless other.is_a?(self.class)
|
|
1345
|
+
self.class.primary_keys.each do |k|
|
|
1346
|
+
return false unless get(k) == other.get(k)
|
|
1347
|
+
end
|
|
1348
|
+
return true
|
|
1349
|
+
end
|
|
1350
|
+
|
|
1351
|
+
##############################################################
|
|
1352
|
+
# Iterators #
|
|
1353
|
+
##############################################################
|
|
1354
|
+
|
|
1355
|
+
# Iterates over elements and yields name-value pairs
|
|
1356
|
+
def each # :yields: element_name, element_value
|
|
1357
|
+
self.class.elements.each do |name, el|
|
|
1358
|
+
yield name, get(name)
|
|
1359
|
+
end
|
|
1360
|
+
end
|
|
1361
|
+
|
|
1362
|
+
# Iterates over non-nil elements, yielding name-value pairs
|
|
1363
|
+
def each_val # :yields: element_name, element_value
|
|
1364
|
+
self.class.elements.select{ |name, el| element_has_value?(name) }.each do |name, el|
|
|
1365
|
+
yield name, get(name)
|
|
1366
|
+
end
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
# Returns an array of current primary key values
|
|
1370
|
+
def primary_keys
|
|
1371
|
+
self.class.primary_keys.map{ |k|
|
|
1372
|
+
k.model? ? get(k).primary_keys : get(k)
|
|
1373
|
+
}
|
|
1374
|
+
end
|
|
1375
|
+
|
|
1376
|
+
# Returns an hash of primary keys names and values
|
|
1377
|
+
def primary_keys_hash
|
|
1378
|
+
h = {}
|
|
1379
|
+
self.class.primary_keys.each{ |k| h[k.name] = get(k) }
|
|
1380
|
+
h
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1383
|
+
# Returns a string with the primary keys joined by ','
|
|
1384
|
+
def keys_string
|
|
1385
|
+
self.class.primary_keys.map{ |pk| self.get(pk) }.join(',')
|
|
1386
|
+
end
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
|
|
1390
|
+
# Returns true if the element instance variable is set
|
|
1391
|
+
#--
|
|
1392
|
+
# FIXME: should probably try to get away without this method
|
|
1393
|
+
# it is the only method that relies on the mapper
|
|
1394
|
+
def element_has_value?(element)
|
|
1395
|
+
element_name = (element.is_a?(Element)) ? element.name : element
|
|
1396
|
+
element = self.class.elements[element_name]
|
|
1397
|
+
if (element.integrated?)
|
|
1398
|
+
return false unless obj = instance_variable_get(:"@#{element.integrated_from.name}")
|
|
1399
|
+
return obj.element_has_value?(element.integrated_from_element)
|
|
1400
|
+
end
|
|
1401
|
+
if (element.attributes[:computed_from])
|
|
1402
|
+
element.attributes[:computed_from].each{ |el| return false unless element_has_value?(el) }
|
|
1403
|
+
return true
|
|
1404
|
+
end
|
|
1405
|
+
ivar = instance_variable_get(:"@#{element_name}")
|
|
1406
|
+
return ivar == nil ? false : true
|
|
1407
|
+
# FIXME: is this needed?
|
|
1408
|
+
# if (!mapper.mapped?(element)
|
|
1409
|
+
# return send("#{element_name}?") if (respond_to?("#{element_name}?"))
|
|
1410
|
+
# return get(element) == nil ? false : true if (!mapper.mapped?(element))
|
|
1411
|
+
# end
|
|
1412
|
+
end
|
|
1413
|
+
|
|
1414
|
+
# Returns true if the element value has been modified since instantiating or loading
|
|
1415
|
+
def element_modified?(element)
|
|
1416
|
+
element = element.is_a?(Element) ? element : self.class.elements[element]
|
|
1417
|
+
set_mod = @modified_elements[element.name]
|
|
1418
|
+
return set_mod if set_mod
|
|
1419
|
+
if (element.integrated?)
|
|
1420
|
+
return false unless integrated = get_no_load(element.integrated_from)
|
|
1421
|
+
return integrated.element_modified?(element.integrated_from_element)
|
|
1422
|
+
end
|
|
1423
|
+
if element_has_value?(element) && (val = get(element)).respond_to?(:modified?)
|
|
1424
|
+
return val.modified?
|
|
1425
|
+
end
|
|
1426
|
+
return false
|
|
1427
|
+
end
|
|
1428
|
+
|
|
1429
|
+
# Returns true if any of elements has been modified
|
|
1430
|
+
def elements_modified?(*elements)
|
|
1431
|
+
elements.each{ |el| return true if element_modified?(el) }
|
|
1432
|
+
return false
|
|
1433
|
+
end
|
|
1434
|
+
|
|
1435
|
+
# Returns true if any element, or any child object, has been modified
|
|
1436
|
+
def modified?
|
|
1437
|
+
return true unless @modified_elements.reject{ |key, val| !val }.empty?
|
|
1438
|
+
self.class.elements_array.select{ |el|
|
|
1439
|
+
!el.model? && element_has_value?(el) && el.type.is_a?(Spider::DataType)
|
|
1440
|
+
}.each do |el|
|
|
1441
|
+
return true if get(el).modified?
|
|
1442
|
+
end
|
|
1443
|
+
return false
|
|
1444
|
+
end
|
|
1445
|
+
|
|
1446
|
+
def in_storage?
|
|
1447
|
+
return false unless primary_keys_set?
|
|
1448
|
+
return self.class.load(primary_keys_hash)
|
|
1449
|
+
end
|
|
1450
|
+
|
|
1451
|
+
# Given elements are set as modified
|
|
1452
|
+
def set_modified(request) #:nodoc:
|
|
1453
|
+
request.each do |key, val| # FIXME: go deep
|
|
1454
|
+
@modified_elements[key] = true
|
|
1455
|
+
end
|
|
1456
|
+
end
|
|
1457
|
+
|
|
1458
|
+
# Resets modified elements
|
|
1459
|
+
def reset_modified_elements(*elements) #:nodoc:
|
|
1460
|
+
if (elements.length > 0)
|
|
1461
|
+
elements.each{ |el_name| @modified_elements.delete(el_name) }
|
|
1462
|
+
else
|
|
1463
|
+
@modified_elements = {}
|
|
1464
|
+
end
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1467
|
+
# Returns true if all primary keys have a value; false if some primary key
|
|
1468
|
+
# is not set or the model has no primary key
|
|
1469
|
+
def primary_keys_set?
|
|
1470
|
+
primary_keys = self.class.primary_keys
|
|
1471
|
+
return false unless primary_keys.length > 0
|
|
1472
|
+
primary_keys.each do |el|
|
|
1473
|
+
if (el.integrated?)
|
|
1474
|
+
return false unless (int_obj = instance_variable_get(:"@#{el.integrated_from.name}"))
|
|
1475
|
+
#return false unless int_obj.instance_variable_get(:"@#{el.integrated_from_element}")
|
|
1476
|
+
return false unless int_obj.element_has_value?(el.integrated_from_element)
|
|
1477
|
+
else
|
|
1478
|
+
return false unless self.instance_variable_get(:"@#{el.name}")
|
|
1479
|
+
end
|
|
1480
|
+
end
|
|
1481
|
+
return true
|
|
1482
|
+
end
|
|
1483
|
+
|
|
1484
|
+
# Returns true if no element has a value
|
|
1485
|
+
def empty?
|
|
1486
|
+
return @_has_values
|
|
1487
|
+
end
|
|
1488
|
+
|
|
1489
|
+
# Sets all values of obj on the current object, cloning them if possible
|
|
1490
|
+
def merge!(obj)
|
|
1491
|
+
obj.class.elements_array.select{ |el|
|
|
1492
|
+
obj.element_has_value?(el) && !el.integrated? && !el.attributes[:computed_from]
|
|
1493
|
+
}.each do |el|
|
|
1494
|
+
val = obj.get(el)
|
|
1495
|
+
if (!val.is_a?(BaseModel) && val.respond_to?(:clone))
|
|
1496
|
+
begin; val = val.clone; rescue TypeError; end;
|
|
1497
|
+
end
|
|
1498
|
+
set_loaded_value(el, val)
|
|
1499
|
+
end
|
|
1500
|
+
@loaded_elements.merge!(obj.loaded_elements)
|
|
1501
|
+
end
|
|
1502
|
+
|
|
1503
|
+
# Returns a deep copy of the object
|
|
1504
|
+
def clone
|
|
1505
|
+
obj = self.class.new
|
|
1506
|
+
obj.merge!(self)
|
|
1507
|
+
return obj
|
|
1508
|
+
end
|
|
1509
|
+
|
|
1510
|
+
# Returns a new instance with the same primary keys
|
|
1511
|
+
def get_new
|
|
1512
|
+
obj = self.class.new
|
|
1513
|
+
self.class.primary_keys.each{ |k| obj.set(k, self.get(k)) }
|
|
1514
|
+
return obj
|
|
1515
|
+
end
|
|
1516
|
+
|
|
1517
|
+
# Returns a new static instance with the same primary keys
|
|
1518
|
+
def get_new_static
|
|
1519
|
+
obj = self.class.static
|
|
1520
|
+
self.class.primary_keys.each{ |k| obj.set(k, self.get(k)) }
|
|
1521
|
+
return obj
|
|
1522
|
+
end
|
|
1523
|
+
|
|
1524
|
+
# Returns a condition based on the current primary keys
|
|
1525
|
+
def keys_to_condition
|
|
1526
|
+
c = Condition.and
|
|
1527
|
+
self.class.primary_keys.each do |key|
|
|
1528
|
+
val = get(key)
|
|
1529
|
+
if (key.model?)
|
|
1530
|
+
c[key.name] = val.keys_to_condition
|
|
1531
|
+
else
|
|
1532
|
+
c[key.name] = val
|
|
1533
|
+
end
|
|
1534
|
+
end
|
|
1535
|
+
return c
|
|
1536
|
+
end
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
#################################################
|
|
1540
|
+
# Object observers methods #
|
|
1541
|
+
#################################################
|
|
1542
|
+
|
|
1543
|
+
# The given block will be called whenever a value is modified.
|
|
1544
|
+
# The block will be passed three arguments: the object, the element name, and the previous value
|
|
1545
|
+
# Example:
|
|
1546
|
+
# obj.observe_all_values do |instance, element_name, old_val|
|
|
1547
|
+
# puts "#{element_name} for object #{instance} has changed from #{old_val} to #{instance.get(element_name) }"
|
|
1548
|
+
def observe_all_values(&proc)
|
|
1549
|
+
@all_values_observers << proc
|
|
1550
|
+
end
|
|
1551
|
+
|
|
1552
|
+
def observe_element(element_name, &proc)
|
|
1553
|
+
@value_observers[element_name] ||= []
|
|
1554
|
+
@value_observers[element_name] << proc
|
|
1555
|
+
end
|
|
1556
|
+
|
|
1557
|
+
def self.observer_all_values(&proc)
|
|
1558
|
+
@all_values_observers << proc
|
|
1559
|
+
end
|
|
1560
|
+
|
|
1561
|
+
def self.observe_element(element_name, &proc)
|
|
1562
|
+
self.value_observers[element_name] ||= []
|
|
1563
|
+
@value_observers[element_name] << proc
|
|
1564
|
+
end
|
|
1565
|
+
|
|
1566
|
+
def self.value_observers
|
|
1567
|
+
@value_observers ||= {}
|
|
1568
|
+
end
|
|
1569
|
+
|
|
1570
|
+
def self.all_values_observers
|
|
1571
|
+
@all_values_observers ||= []
|
|
1572
|
+
end
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
# Calls the observers for element_name
|
|
1576
|
+
def notify_observers(element_name, new_val) #:nodoc:
|
|
1577
|
+
(self.class.value_observers[element_name].to_a + @value_observers[element_name].to_a) \
|
|
1578
|
+
.each { |proc| proc.call(self, element_name, new_val) }
|
|
1579
|
+
(self.class.all_values_observers.to_a + @all_values_observers.to_a).each { |proc| proc.call(self, element_name, new_val) }
|
|
1580
|
+
end
|
|
1581
|
+
|
|
1582
|
+
|
|
1583
|
+
|
|
1584
|
+
##############################################################
|
|
1585
|
+
# Storage, mapper and schema loading (instance methods) #
|
|
1586
|
+
##############################################################
|
|
1587
|
+
|
|
1588
|
+
# Returns the current @storage, or instantiates the default calling Spider::BaseModel.storage
|
|
1589
|
+
def storage
|
|
1590
|
+
return @storage || self.class.storage
|
|
1591
|
+
end
|
|
1592
|
+
|
|
1593
|
+
# Instantiates the storage for the instance.
|
|
1594
|
+
# Accepts a string (url or named storage) which will be passed to Spider::BaseModel.get_storage
|
|
1595
|
+
# Example:
|
|
1596
|
+
# obj.use_storage('my_named_db')
|
|
1597
|
+
# obj.use_storage('db:oracle://username:password@XE')
|
|
1598
|
+
def use_storage(storage)
|
|
1599
|
+
@storage = self.class.get_storage(storage)
|
|
1600
|
+
@mapper = self.class.get_mapper(@storage)
|
|
1601
|
+
end
|
|
1602
|
+
|
|
1603
|
+
# Returns the current mapper, or instantiates a new one (base on the current storage, if set)
|
|
1604
|
+
def mapper
|
|
1605
|
+
if (@storage)
|
|
1606
|
+
@mapper ||= self.class.get_mapper(@storage)
|
|
1607
|
+
else
|
|
1608
|
+
@mapper ||= self.class.mapper
|
|
1609
|
+
end
|
|
1610
|
+
return @mapper
|
|
1611
|
+
end
|
|
1612
|
+
|
|
1613
|
+
# Sets the current mapper
|
|
1614
|
+
def mapper=(mapper)
|
|
1615
|
+
@mapper = mapper
|
|
1616
|
+
end
|
|
1617
|
+
|
|
1618
|
+
##############################################################
|
|
1619
|
+
# Saving and loading from storage methods #
|
|
1620
|
+
##############################################################
|
|
1621
|
+
|
|
1622
|
+
# Saves the object to the storage
|
|
1623
|
+
# (see Mapper#save)
|
|
1624
|
+
def save
|
|
1625
|
+
mapper.save(self)
|
|
1626
|
+
reset_modified_elements
|
|
1627
|
+
end
|
|
1628
|
+
|
|
1629
|
+
# Saves the object and all child objects to the storage
|
|
1630
|
+
# (see Mapper#save_all)
|
|
1631
|
+
def save_all
|
|
1632
|
+
mapper.save_all(self)
|
|
1633
|
+
end
|
|
1634
|
+
|
|
1635
|
+
# Inserts the object in the storage
|
|
1636
|
+
# Note: if the object is already present in the storage and unique indexes are enforced,
|
|
1637
|
+
# this will raise an error.
|
|
1638
|
+
# (See Mapper#insert).
|
|
1639
|
+
def insert
|
|
1640
|
+
mapper.insert(self)
|
|
1641
|
+
reset_modified_elements
|
|
1642
|
+
end
|
|
1643
|
+
|
|
1644
|
+
# Updates the object in the storage
|
|
1645
|
+
# Note: the update will silently fail if the object is not present in the storage
|
|
1646
|
+
# (see Mapper#update).
|
|
1647
|
+
def update
|
|
1648
|
+
mapper.update(self)
|
|
1649
|
+
reset_modified_elements
|
|
1650
|
+
end
|
|
1651
|
+
|
|
1652
|
+
# Deletes the object from the storage
|
|
1653
|
+
# (see Mapper#delete).
|
|
1654
|
+
def delete
|
|
1655
|
+
mapper.delete(self)
|
|
1656
|
+
end
|
|
1657
|
+
|
|
1658
|
+
# Loads the object from the storage
|
|
1659
|
+
# Acceptable arguments are:
|
|
1660
|
+
# * a Query object, or
|
|
1661
|
+
# * a Request object, or a Hash, which will be converted to a Request, or
|
|
1662
|
+
# * a list of elements to request
|
|
1663
|
+
# It will then construct a Condition with current primary keys, and call Mapper#load
|
|
1664
|
+
# Note that an error will be raised by the Mapper if not all primary keys are set.
|
|
1665
|
+
def load(*params)
|
|
1666
|
+
if (params[0].is_a? Query)
|
|
1667
|
+
query = params[0]
|
|
1668
|
+
else
|
|
1669
|
+
return false unless primary_keys_set?
|
|
1670
|
+
query = Query.new
|
|
1671
|
+
if (params[0].is_a?(Request))
|
|
1672
|
+
query.request = params.shift
|
|
1673
|
+
elsif (params[0].is_a?(Hash))
|
|
1674
|
+
query.request = Request.new(params.shift)
|
|
1675
|
+
end
|
|
1676
|
+
|
|
1677
|
+
elements = params.length > 0 ? params : self.class.elements.keys
|
|
1678
|
+
return true unless elements.select{ |el| !element_loaded?(el) }.length > 0
|
|
1679
|
+
elements.each do |name|
|
|
1680
|
+
query.request[name] = true
|
|
1681
|
+
end
|
|
1682
|
+
query.condition.conjunction = :and
|
|
1683
|
+
self.class.primary_keys.each do |key|
|
|
1684
|
+
query.condition[key.name] = get(key.name)
|
|
1685
|
+
end
|
|
1686
|
+
end
|
|
1687
|
+
#clear_values()
|
|
1688
|
+
return mapper.load(self, query)
|
|
1689
|
+
end
|
|
1690
|
+
|
|
1691
|
+
# Sets all values to nil
|
|
1692
|
+
def clear_values()
|
|
1693
|
+
self.class.elements.each_key do |element_name|
|
|
1694
|
+
instance_variable_set(:"@#{element_name}", nil)
|
|
1695
|
+
end
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1698
|
+
def remove_association(element, object)
|
|
1699
|
+
mapper.delete_element_associations(self, element, object)
|
|
1700
|
+
end
|
|
1701
|
+
|
|
1702
|
+
##############################################################
|
|
1703
|
+
# Method missing #
|
|
1704
|
+
##############################################################
|
|
1705
|
+
|
|
1706
|
+
# Tries the method on integrated models
|
|
1707
|
+
def method_missing(method, *args) #:nodoc:
|
|
1708
|
+
# UNUSED
|
|
1709
|
+
# case method.to_s
|
|
1710
|
+
# when /load_by_(.+)/
|
|
1711
|
+
# element = $1
|
|
1712
|
+
# if !self.class.elements[element.to_sym].attributes[:primary_key]
|
|
1713
|
+
# raise ModelException, "load_by_ called for element #{element} which is not a primary key"
|
|
1714
|
+
# elsif self.class.primary_keys.length > 1
|
|
1715
|
+
# raise ModelException, "can't call #{method} because #{element} is not the only primary key"
|
|
1716
|
+
# end
|
|
1717
|
+
# query = Query.new
|
|
1718
|
+
# query.condition[element.to_sym] = args[0]
|
|
1719
|
+
# load(query)
|
|
1720
|
+
# else
|
|
1721
|
+
if (self.class.attributes[:integrated_models])
|
|
1722
|
+
self.class.attributes[:integrated_models].each do |model, name|
|
|
1723
|
+
obj = send(name)
|
|
1724
|
+
if (obj.respond_to?(method))
|
|
1725
|
+
return obj.send(method, *args)
|
|
1726
|
+
end
|
|
1727
|
+
end
|
|
1728
|
+
end
|
|
1729
|
+
super
|
|
1730
|
+
# end
|
|
1731
|
+
end
|
|
1732
|
+
|
|
1733
|
+
# Returns a descriptive string for the object.
|
|
1734
|
+
# By default this method returns the value of the first String element, if any; otherwise,
|
|
1735
|
+
# the string representation of the first element of any type.
|
|
1736
|
+
# Descendant classes may well provide a better representation.
|
|
1737
|
+
def to_s
|
|
1738
|
+
self.class.each_element do |el|
|
|
1739
|
+
if ((el.type == String || el.type == Text) && !el.primary_key?)
|
|
1740
|
+
v = get(el)
|
|
1741
|
+
return v ? v.to_s : ''
|
|
1742
|
+
end
|
|
1743
|
+
end
|
|
1744
|
+
el = self.class.elements_array[0]
|
|
1745
|
+
if element_has_value?(el)
|
|
1746
|
+
v = get(el)
|
|
1747
|
+
return v ? v.to_s : ''
|
|
1748
|
+
end
|
|
1749
|
+
return ''
|
|
1750
|
+
end
|
|
1751
|
+
|
|
1752
|
+
# A compact representation of the object.
|
|
1753
|
+
# Note: inspect will not autoload the object.
|
|
1754
|
+
def inspect
|
|
1755
|
+
self.class.name+': {' +
|
|
1756
|
+
self.class.elements_array.select{ |el| (element_loaded?(el) || element_has_value?(el)) && !el.hidden? } \
|
|
1757
|
+
.map{ |el| ":#{el.name} => #{get(el.name).to_s}"}.join(',') + '}'
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1760
|
+
# Returns a JSON representation of the object.
|
|
1761
|
+
#
|
|
1762
|
+
# The tree will be traversed outputting all encountered objects; when an already seen object
|
|
1763
|
+
# is met, the primary keys will be output (as a single value if one, as an array if many) and traversing
|
|
1764
|
+
# will stop.
|
|
1765
|
+
#
|
|
1766
|
+
# For more fine-grained control of the output, it is better to use the #cut method and call to_json on it.
|
|
1767
|
+
def to_json(state=nil, &proc)
|
|
1768
|
+
require 'json'
|
|
1769
|
+
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
|
|
1770
|
+
if (@tmp_json_seen && !block_given?)
|
|
1771
|
+
pks = self.class.primary_keys.map{ |k| get(k).to_json }
|
|
1772
|
+
pks = pks[0] if pks.length == 1
|
|
1773
|
+
return pks.to_json
|
|
1774
|
+
end
|
|
1775
|
+
@tmp_json_seen = true
|
|
1776
|
+
json = ""
|
|
1777
|
+
#Spider::Model.with_identity_mapper do |im|
|
|
1778
|
+
self.class.elements_array.select{ |el| el.attributes[:integrated_model] }.each do |el|
|
|
1779
|
+
(int = get(el)) && int.instance_variable_set("@tmp_json_seen", true)
|
|
1780
|
+
end
|
|
1781
|
+
if (block_given?)
|
|
1782
|
+
select_elements = Proc.new{ true }
|
|
1783
|
+
else
|
|
1784
|
+
select_elements = Proc.new{ |name, el|
|
|
1785
|
+
!el.hidden?
|
|
1786
|
+
# &&
|
|
1787
|
+
# #!el.attributes[:integrated_model] &&
|
|
1788
|
+
# (element_has_value?(el) || (el.integrated? && element_has_value?(el.integrated_from)))
|
|
1789
|
+
}
|
|
1790
|
+
end
|
|
1791
|
+
|
|
1792
|
+
|
|
1793
|
+
json = "{" +
|
|
1794
|
+
self.class.elements.select(&select_elements).map{ |name, el|
|
|
1795
|
+
if (block_given?)
|
|
1796
|
+
val = yield(self, el)
|
|
1797
|
+
val ? "#{name.to_json}: #{val.to_json}" : nil
|
|
1798
|
+
else
|
|
1799
|
+
val = get(name)
|
|
1800
|
+
if (el.type == 'text' || el.type == 'longText')
|
|
1801
|
+
val = ic.iconv(val + ' ')[0..-2]
|
|
1802
|
+
end
|
|
1803
|
+
val = val.to_json
|
|
1804
|
+
"#{name.to_json}: #{val}"
|
|
1805
|
+
end
|
|
1806
|
+
}.select{ |pair| pair}.join(',') + "}"
|
|
1807
|
+
@tmp_json_seen = false
|
|
1808
|
+
self.class.elements_array.select{ |el| el.attributes[:integrated_model] }.each do |el|
|
|
1809
|
+
(int = get(el)) && int.instance_variable_set("@tmp_json_seen", false)
|
|
1810
|
+
end
|
|
1811
|
+
#end
|
|
1812
|
+
return json
|
|
1813
|
+
end
|
|
1814
|
+
|
|
1815
|
+
# Returns a part of the object tree, converted to Hashes, Arrays and Strings.
|
|
1816
|
+
# Arguments can be:
|
|
1817
|
+
# * a String, followed by a list of elements; the String will be sprintf'd with element values
|
|
1818
|
+
# or
|
|
1819
|
+
# * a depth Fixnum; depth 0 means obj.to_s will be returned, depth 1 will return an hash containing the
|
|
1820
|
+
# object's element values converted to string, and so on
|
|
1821
|
+
# or
|
|
1822
|
+
# * a Hash, whith element names as keys, and depths, or Hashes, or Procs as values; each element
|
|
1823
|
+
# will be traversed up to the depth given, or recursively according to the has; or, if a Proc is given,
|
|
1824
|
+
# it will be called with the current object and element name as arguments
|
|
1825
|
+
# or
|
|
1826
|
+
# * a list of elements; this is equivalent to passing a hash of the elements with depth 0.
|
|
1827
|
+
#
|
|
1828
|
+
# Examples:
|
|
1829
|
+
# obj.inspect
|
|
1830
|
+
# => Zoo::Animal: {:name => Llama, :family => Camelidae, :friends => Sheep, Camel}
|
|
1831
|
+
# obj.cut(0)
|
|
1832
|
+
# => 'Llama'
|
|
1833
|
+
# obj.cut(:name, :friends)
|
|
1834
|
+
# => {:name => 'Llama', :friends => 'Sheep, Camel'}
|
|
1835
|
+
# obj.cut(:name => 0, :friends => 1)
|
|
1836
|
+
# => {:name => 'Llama', :friends => [
|
|
1837
|
+
# {:name => 'Sheep', :family => 'Bovidae', :friends => 'Llama'},
|
|
1838
|
+
# {:name => 'Camel', :family => 'Camelidae', :friens => 'Dromedary, LLama'}
|
|
1839
|
+
# ]}
|
|
1840
|
+
# obj.cut(:name => 0, :friends => {:name => 0})
|
|
1841
|
+
# => {:name => 'Llama', :friends => [{:name => 'Sheep'}, {:name => 'Camel'}]}
|
|
1842
|
+
# objs.cut(:name => 0, :friends => lambda{ |instance, element|
|
|
1843
|
+
# instance.get(element).name.upcase
|
|
1844
|
+
# })
|
|
1845
|
+
# => {:name => 'Llama', :friends => ['SHEEP', 'CAMEL']}
|
|
1846
|
+
# obj.cut("Hi, i'm a %s and my friends are %s", :name, :friends)
|
|
1847
|
+
# => "Hi, i'm a Llama and my friends are Sheep, Camel"
|
|
1848
|
+
def cut(*params, &proc)
|
|
1849
|
+
h = {}
|
|
1850
|
+
if (params[0].is_a?(String))
|
|
1851
|
+
return sprintf(params[0], *params[1..-1].map{ |el| get(el) })
|
|
1852
|
+
elsif (params[0].is_a?(Fixnum))
|
|
1853
|
+
p = params.shift
|
|
1854
|
+
if (p < 1)
|
|
1855
|
+
if (block_given?)
|
|
1856
|
+
return proc.call(self)
|
|
1857
|
+
else
|
|
1858
|
+
return self.to_s
|
|
1859
|
+
end
|
|
1860
|
+
end
|
|
1861
|
+
lev = p
|
|
1862
|
+
where = {}
|
|
1863
|
+
self.class.elements_array.each { |el| where[el.name] = lev-1}
|
|
1864
|
+
end
|
|
1865
|
+
if (params[0].is_a?(Hash))
|
|
1866
|
+
where ||= {}
|
|
1867
|
+
params[0].each{ |k, v| where[k.to_sym] = v}
|
|
1868
|
+
else
|
|
1869
|
+
where ||= {}
|
|
1870
|
+
params.each{ |p| where[p] = 0 if p.is_a?(Symbol)}
|
|
1871
|
+
end
|
|
1872
|
+
Spider::Model.with_identity_mapper do |im|
|
|
1873
|
+
where.keys.each do |name|
|
|
1874
|
+
if (where[name].is_a?(Proc))
|
|
1875
|
+
val = where[name].call(self, name)
|
|
1876
|
+
else
|
|
1877
|
+
el = self.class.elements[name]
|
|
1878
|
+
if el
|
|
1879
|
+
val = get(el)
|
|
1880
|
+
val = val.cut(where[name], &proc) if el.model? && val
|
|
1881
|
+
else
|
|
1882
|
+
raise ModelException, "Element #{name} does not exist" unless self.respond_to?(name)
|
|
1883
|
+
val = self.send(name)
|
|
1884
|
+
val = val.cut(where[name], &proc) if val.is_a?(BaseModel)
|
|
1885
|
+
end
|
|
1886
|
+
end
|
|
1887
|
+
h[name] = val
|
|
1888
|
+
end
|
|
1889
|
+
end
|
|
1890
|
+
return h
|
|
1891
|
+
end
|
|
1892
|
+
|
|
1893
|
+
# Returns a element_name => value Hash
|
|
1894
|
+
def to_hash()
|
|
1895
|
+
h = {}
|
|
1896
|
+
self.class.elements.select{ |name, el| element_loaded? el }.each do |name, el|
|
|
1897
|
+
h[name] = get(name)
|
|
1898
|
+
end
|
|
1899
|
+
return h
|
|
1900
|
+
end
|
|
1901
|
+
|
|
1902
|
+
# Returns a yaml representation of the object. Will try to autoload all elements, unless autoload is false;
|
|
1903
|
+
# foreign keys will be expressed as an array if multiple, as a single primary key value otherwise
|
|
1904
|
+
def to_yaml(params)
|
|
1905
|
+
require 'yaml'
|
|
1906
|
+
#return YAML::dump(self)
|
|
1907
|
+
h = {}
|
|
1908
|
+
def obj_pks(obj, klass)
|
|
1909
|
+
unless obj
|
|
1910
|
+
return klass.primary_keys.length > 1 ? [] : nil
|
|
1911
|
+
end
|
|
1912
|
+
pks = obj.primary_keys
|
|
1913
|
+
return pks[0] if pks.length == 1
|
|
1914
|
+
return pks
|
|
1915
|
+
end
|
|
1916
|
+
self.class.elements_array.each do |el|
|
|
1917
|
+
next if params[:except] && params[:except].include?(el.name)
|
|
1918
|
+
if (el.model?)
|
|
1919
|
+
obj = get(el)
|
|
1920
|
+
if (el.multiple?)
|
|
1921
|
+
h[el.name] = obj.map_array{ |o| obj_pks(o, el.model) }
|
|
1922
|
+
else
|
|
1923
|
+
h[el.name] = obj_pks(obj, el.model)
|
|
1924
|
+
end
|
|
1925
|
+
else
|
|
1926
|
+
h[el.name] = get(el)
|
|
1927
|
+
end
|
|
1928
|
+
end
|
|
1929
|
+
return YAML::dump(h)
|
|
1930
|
+
end
|
|
1931
|
+
|
|
1932
|
+
def self.from_yaml(yaml)
|
|
1933
|
+
h = YAML::load(yaml)
|
|
1934
|
+
obj = self.static
|
|
1935
|
+
h.each do |key, value|
|
|
1936
|
+
el = elements[key.to_sym]
|
|
1937
|
+
if (el.multiple?)
|
|
1938
|
+
el_obj = el.model.static
|
|
1939
|
+
el.model.primary_keys.each do |pk|
|
|
1940
|
+
el_obj.set(pk, value.unshift)
|
|
1941
|
+
end
|
|
1942
|
+
obj.set(el, el_obj)
|
|
1943
|
+
else
|
|
1944
|
+
obj.set(el, value)
|
|
1945
|
+
end
|
|
1946
|
+
end
|
|
1947
|
+
return obj
|
|
1948
|
+
end
|
|
1949
|
+
|
|
1950
|
+
def self.transaction
|
|
1951
|
+
yield
|
|
1952
|
+
end
|
|
1953
|
+
|
|
1954
|
+
def self.dump_element(el)
|
|
1955
|
+
remove_elements = []
|
|
1956
|
+
method = case el.attributes[:association]
|
|
1957
|
+
when :many
|
|
1958
|
+
:many
|
|
1959
|
+
when :choice
|
|
1960
|
+
:choice
|
|
1961
|
+
when :multiple_choice
|
|
1962
|
+
:multiple_choice
|
|
1963
|
+
when :tree
|
|
1964
|
+
:tree
|
|
1965
|
+
else
|
|
1966
|
+
:element
|
|
1967
|
+
end
|
|
1968
|
+
type = el.type
|
|
1969
|
+
attributes = el.attributes.clone
|
|
1970
|
+
if (method == :many || method == :multiple_choice)
|
|
1971
|
+
attributes.delete(:multiple)
|
|
1972
|
+
end
|
|
1973
|
+
attributes.delete(:association) if method != :element
|
|
1974
|
+
if (attributes[:association_type])
|
|
1975
|
+
attributes[:through] = attributes[:association_type] unless attributes[:anonymous_model]
|
|
1976
|
+
attributes.delete(:association_type)
|
|
1977
|
+
end
|
|
1978
|
+
attributes.delete(:lazy) if attributes[:lazy] == :default
|
|
1979
|
+
if (method == :tree)
|
|
1980
|
+
delete_attrs = [:queryset_module, :multiple]
|
|
1981
|
+
delete_attrs.each{ |a| attributes.delete(a) }
|
|
1982
|
+
remove_elements += [attributes[:reverse], attributes[:tree_left], attributes[:tree_right], attributes[:tree_depth]]
|
|
1983
|
+
type = nil
|
|
1984
|
+
end
|
|
1985
|
+
return {
|
|
1986
|
+
:name => el.name,
|
|
1987
|
+
:type => type,
|
|
1988
|
+
:attributes => attributes,
|
|
1989
|
+
:method => method,
|
|
1990
|
+
:remove_elements => remove_elements
|
|
1991
|
+
}
|
|
1992
|
+
end
|
|
1993
|
+
|
|
1994
|
+
def self.prepare_to_code
|
|
1995
|
+
modules = self.name.split('::')[0..-2]
|
|
1996
|
+
included = (self.included_modules - Spider::Model::BaseModel.included_modules).select do |m|
|
|
1997
|
+
m.name !~ /^#{Regexp.quote(self.name)}/
|
|
1998
|
+
end
|
|
1999
|
+
local_name = self.name.split('::')[-1]
|
|
2000
|
+
superklass = self.superclass.name
|
|
2001
|
+
elements = []
|
|
2002
|
+
remove_elements = []
|
|
2003
|
+
self.elements_array.each do |el|
|
|
2004
|
+
next if el.integrated?
|
|
2005
|
+
next if (el.reverse && el.model.elements[el.reverse] && \
|
|
2006
|
+
(el.model.elements[el.reverse].attributes[:add_reverse] || \
|
|
2007
|
+
el.model.elements[el.reverse].attributes[:add_multiple_reverse]))
|
|
2008
|
+
el_hash = dump_element(el)
|
|
2009
|
+
return nil unless el_hash
|
|
2010
|
+
elements << el_hash
|
|
2011
|
+
remove_elements += el_hash[:remove_elements]
|
|
2012
|
+
end
|
|
2013
|
+
elements.reject!{ |el| remove_elements.include?(el[:name]) }
|
|
2014
|
+
return {
|
|
2015
|
+
:modules => modules,
|
|
2016
|
+
:included => included,
|
|
2017
|
+
:attributes => self.attributes,
|
|
2018
|
+
:elements => elements,
|
|
2019
|
+
:local_name => local_name,
|
|
2020
|
+
:superclass => superklass,
|
|
2021
|
+
:use_storage => @use_storage,
|
|
2022
|
+
:additional_code => []
|
|
2023
|
+
}
|
|
2024
|
+
end
|
|
2025
|
+
|
|
2026
|
+
def self.to_code(options={})
|
|
2027
|
+
c = prepare_to_code
|
|
2028
|
+
str = ""
|
|
2029
|
+
indent = 0
|
|
2030
|
+
append = lambda do |val|
|
|
2031
|
+
str += " "*indent
|
|
2032
|
+
str += val
|
|
2033
|
+
str
|
|
2034
|
+
end
|
|
2035
|
+
str += c[:modules].map{ |m| "module #{m}" }.join('; ') + "\n"
|
|
2036
|
+
str += "\n"
|
|
2037
|
+
indent = 4
|
|
2038
|
+
append.call "class #{c[:local_name]} < #{c[:superclass]}\n"
|
|
2039
|
+
indent += 4
|
|
2040
|
+
c[:included].each do |i|
|
|
2041
|
+
append.call "include #{i.name}\n"
|
|
2042
|
+
end
|
|
2043
|
+
c[:attributes].each do |k, v|
|
|
2044
|
+
append.call "attribute :#{k}, #{v.inspect}"
|
|
2045
|
+
end
|
|
2046
|
+
str += "\n"
|
|
2047
|
+
c[:elements].each do |el|
|
|
2048
|
+
append.call("#{el[:method].to_s} #{el[:name].inspect}")
|
|
2049
|
+
str += ", #{el[:type]}" if el[:type]
|
|
2050
|
+
str += ", #{el[:attributes].inspect[1..-2]}\n" if el[:attributes] && !el[:attributes].empty?
|
|
2051
|
+
end
|
|
2052
|
+
str += "\n"
|
|
2053
|
+
append.call "use_storage '#{c[:use_storage]}'\n" if c[:use_storage]
|
|
2054
|
+
c[:additional_code].each do |block|
|
|
2055
|
+
block.each_line do |line|
|
|
2056
|
+
append.call line
|
|
2057
|
+
end
|
|
2058
|
+
str += "\n"
|
|
2059
|
+
end
|
|
2060
|
+
indent -= 4
|
|
2061
|
+
append.call("end\n")
|
|
2062
|
+
str += c[:modules].map{ "end" }.join(';')
|
|
2063
|
+
return str
|
|
2064
|
+
end
|
|
2065
|
+
|
|
2066
|
+
end
|
|
2067
|
+
|
|
2068
|
+
end; end
|