rear 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +7 -0
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile +20 -0
  5. data/LICENSE +19 -0
  6. data/README.md +101 -0
  7. data/Rakefile +79 -0
  8. data/assets/api.js +307 -0
  9. data/assets/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css +8 -0
  10. data/assets/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js +26 -0
  11. data/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
  12. data/assets/bootstrap/css/bootstrap.min.css +9 -0
  13. data/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
  14. data/assets/bootstrap/img/glyphicons-halflings.png +0 -0
  15. data/assets/bootstrap/js/bootstrap.min.js +6 -0
  16. data/assets/jquery.js +5 -0
  17. data/assets/noty/jquery.noty.js +520 -0
  18. data/assets/noty/layouts/top.js +34 -0
  19. data/assets/noty/layouts/topRight.js +43 -0
  20. data/assets/noty/promise.js +432 -0
  21. data/assets/noty/themes/default.js +156 -0
  22. data/assets/select2-bootstrap.css +86 -0
  23. data/assets/select2/select2-spinner.gif +0 -0
  24. data/assets/select2/select2.css +652 -0
  25. data/assets/select2/select2.min.js +22 -0
  26. data/assets/select2/select2.png +0 -0
  27. data/assets/select2/select2x2.png +0 -0
  28. data/assets/ui.css +75 -0
  29. data/assets/xhr.js +4 -0
  30. data/bin/rear +65 -0
  31. data/docs/Assocs.md +100 -0
  32. data/docs/Columns.md +404 -0
  33. data/docs/Deploy.md +62 -0
  34. data/docs/FileManager.md +75 -0
  35. data/docs/Filters.md +341 -0
  36. data/docs/Setup.md +201 -0
  37. data/lib/rear.rb +13 -0
  38. data/lib/rear/actions.rb +98 -0
  39. data/lib/rear/constants.rb +61 -0
  40. data/lib/rear/controller_setup.rb +249 -0
  41. data/lib/rear/helpers.rb +17 -0
  42. data/lib/rear/helpers/class.rb +46 -0
  43. data/lib/rear/helpers/columns.rb +68 -0
  44. data/lib/rear/helpers/filters.rb +147 -0
  45. data/lib/rear/helpers/generic.rb +73 -0
  46. data/lib/rear/helpers/order.rb +47 -0
  47. data/lib/rear/helpers/pager.rb +35 -0
  48. data/lib/rear/helpers/render.rb +37 -0
  49. data/lib/rear/home_controller.rb +10 -0
  50. data/lib/rear/input.rb +341 -0
  51. data/lib/rear/orm.rb +73 -0
  52. data/lib/rear/rear.rb +74 -0
  53. data/lib/rear/setup.rb +9 -0
  54. data/lib/rear/setup/associations.rb +33 -0
  55. data/lib/rear/setup/columns.rb +109 -0
  56. data/lib/rear/setup/filters.rb +314 -0
  57. data/lib/rear/setup/generic.rb +59 -0
  58. data/lib/rear/setup/menu.rb +39 -0
  59. data/lib/rear/templates/editor/ace.slim +7 -0
  60. data/lib/rear/templates/editor/assocs.slim +10 -0
  61. data/lib/rear/templates/editor/boolean.slim +5 -0
  62. data/lib/rear/templates/editor/bulk_edit.slim +75 -0
  63. data/lib/rear/templates/editor/checkbox.slim +5 -0
  64. data/lib/rear/templates/editor/ckeditor.slim +7 -0
  65. data/lib/rear/templates/editor/date.slim +9 -0
  66. data/lib/rear/templates/editor/datetime.slim +9 -0
  67. data/lib/rear/templates/editor/layout.slim +103 -0
  68. data/lib/rear/templates/editor/password.slim +1 -0
  69. data/lib/rear/templates/editor/radio.slim +5 -0
  70. data/lib/rear/templates/editor/select.slim +3 -0
  71. data/lib/rear/templates/editor/string.slim +1 -0
  72. data/lib/rear/templates/editor/text.slim +1 -0
  73. data/lib/rear/templates/editor/time.slim +9 -0
  74. data/lib/rear/templates/error.slim +36 -0
  75. data/lib/rear/templates/filters/boolean.slim +10 -0
  76. data/lib/rear/templates/filters/checkbox.slim +15 -0
  77. data/lib/rear/templates/filters/date.slim +10 -0
  78. data/lib/rear/templates/filters/datetime.slim +10 -0
  79. data/lib/rear/templates/filters/layout.slim +26 -0
  80. data/lib/rear/templates/filters/radio.slim +14 -0
  81. data/lib/rear/templates/filters/select.slim +9 -0
  82. data/lib/rear/templates/filters/string.slim +3 -0
  83. data/lib/rear/templates/filters/text.slim +3 -0
  84. data/lib/rear/templates/filters/time.slim +10 -0
  85. data/lib/rear/templates/home.slim +0 -0
  86. data/lib/rear/templates/layout.slim +78 -0
  87. data/lib/rear/templates/pager.slim +22 -0
  88. data/lib/rear/templates/pane/ace.slim +2 -0
  89. data/lib/rear/templates/pane/assocs.slim +62 -0
  90. data/lib/rear/templates/pane/boolean.slim +2 -0
  91. data/lib/rear/templates/pane/checkbox.slim +5 -0
  92. data/lib/rear/templates/pane/ckeditor.slim +2 -0
  93. data/lib/rear/templates/pane/date.slim +2 -0
  94. data/lib/rear/templates/pane/datetime.slim +2 -0
  95. data/lib/rear/templates/pane/layout.slim +111 -0
  96. data/lib/rear/templates/pane/password.slim +2 -0
  97. data/lib/rear/templates/pane/quickview.slim +21 -0
  98. data/lib/rear/templates/pane/radio.slim +5 -0
  99. data/lib/rear/templates/pane/select.slim +5 -0
  100. data/lib/rear/templates/pane/string.slim +2 -0
  101. data/lib/rear/templates/pane/text.slim +2 -0
  102. data/lib/rear/templates/pane/time.slim +2 -0
  103. data/lib/rear/utils.rb +288 -0
  104. data/rear.gemspec +27 -0
  105. data/test/helpers.rb +33 -0
  106. data/test/models/ar.rb +52 -0
  107. data/test/models/dm.rb +53 -0
  108. data/test/models/sq.rb +58 -0
  109. data/test/setup.rb +4 -0
  110. data/test/templates/adhoc/book/editor/name.slim +1 -0
  111. data/test/templates/adhoc/book/editor/string.slim +1 -0
  112. data/test/templates/adhoc/book/pane/name.slim +1 -0
  113. data/test/templates/adhoc/book/pane/string.slim +1 -0
  114. data/test/templates/shared/shared-templates/editor/string.slim +1 -0
  115. data/test/templates/shared/shared-templates/pane/string.slim +1 -0
  116. data/test/test__assocs.rb +249 -0
  117. data/test/test__columns.rb +269 -0
  118. data/test/test__crud.rb +81 -0
  119. data/test/test__custom_templates.rb +147 -0
  120. data/test/test__filters.rb +228 -0
  121. metadata +220 -0
data/docs/Setup.md ADDED
@@ -0,0 +1,201 @@
1
+
2
+ ## Primary key
3
+
4
+ **Rear** will do its best to correctly detect the primary key of managed model.
5
+
6
+ In case it is failing to do so, you can set primary key manually by using `primary_key` method:
7
+
8
+ ```ruby
9
+ primary_key :ItemID
10
+ ```
11
+
12
+ **[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
13
+
14
+ ## Ordering
15
+
16
+ **Rear** will fetch items in the order specified by your model or db.
17
+
18
+ It is also allow to set specific order by using `order_by` method:
19
+
20
+ `DataMapper`:
21
+
22
+ ```ruby
23
+ order_by :name, :id.desc
24
+ ```
25
+
26
+ `ActiveRecord`:
27
+
28
+ ```ruby
29
+ order_by 'name, id DESC'
30
+ ```
31
+
32
+ Also, on pane pages it is possible to order items by a specific column by clicking on it.
33
+ By default it will sort items in ascending order. To use descending order click on column one more time.
34
+
35
+ When sorting items this way, "ORDER BY" statement will use only selected column,
36
+ e.g. "ORDER BY name" or "ORDER BY date" etc.
37
+
38
+ To make Rear to sort by multiple columns when clicking on a specific one, set custom `order_by` for that column:
39
+
40
+ ```ruby
41
+ class News
42
+ include DataMapper::Resource
43
+
44
+ property :id, Serial
45
+ property :name, String
46
+ property :date, Date
47
+
48
+ end
49
+
50
+ Rear.register News do
51
+
52
+ # making Rear to order by both name and date when name clicked
53
+ input :name do
54
+ order_by :name, :date
55
+ end
56
+ end
57
+ ```
58
+
59
+ **Important!** do not pass ordering vector when setting costom `order_by` for columns. Vector will be added automatically, so pass only column names.
60
+
61
+ **Example:** this will break ordering cause name specifies desc vector
62
+
63
+ ```ruby
64
+ input :name do
65
+ order_by :name.desc, :date
66
+ end
67
+ ```
68
+
69
+
70
+
71
+ **[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
72
+
73
+
74
+ ## Items per page
75
+
76
+ By default **Rear** will display 10 items per page.
77
+
78
+ To have it displaying more or less, use `items_per_page` method or its alias - `ipp`:
79
+
80
+ ```ruby
81
+ items_per_page 50
82
+ ```
83
+
84
+ **[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
85
+
86
+
87
+ ## Menu Label
88
+
89
+ **Rear** will build a menu that will include all managed models.
90
+
91
+ By default, model name will be used as label:
92
+
93
+ ```ruby
94
+ class PageModel < ActiveRecord::Base
95
+ # ...
96
+ end
97
+
98
+ Rear.register PageModel
99
+ ```
100
+
101
+ This will display "PageModel" in menu.
102
+
103
+ To have a custom label displayed, use `menu_label` method:
104
+
105
+ ```ruby
106
+ Rear.register PageModel do
107
+ menu_label :Pages
108
+ # ...
109
+ end
110
+ ```
111
+
112
+ Now it will display "Pages" in menu.
113
+
114
+ It is also possible to use `label` alias to set menu label.
115
+
116
+ **[ [contents &uarr;](https://github.com/espresso/rear#tutorial) ]**
117
+
118
+
119
+ ## Menu Positioning
120
+
121
+ **Rear's** menu will display managed models in the the order they was defined.
122
+
123
+ To have a model displayed upper, set its position higher.
124
+
125
+ Or decrease position to have a model displayed lower.
126
+
127
+ ```ruby
128
+ class City < ActiveRecord::Base
129
+ # ...
130
+ end
131
+
132
+ class Country < ActiveRecord::Base
133
+ # ...
134
+ end
135
+
136
+ class State < ActiveRecord::Base
137
+ # ...
138
+ end
139
+
140
+ Rear.register City, Country, State
141
+ ```
142
+
143
+ This will build a menu like `City | Country | State`.
144
+
145
+ ```ruby
146
+ Rear.register City, Country, State do |model|
147
+ menu_position({
148
+ Country => 1000,
149
+ State => 500,
150
+ City => 100,
151
+ }[model])
152
+ end
153
+ ```
154
+
155
+ This will build a menu like `Country | State | City`.
156
+
157
+ It is also possible to use `position` alias to set menu position.
158
+
159
+ **[ [contents &uarr;](https://github.com/espresso/rear#tutorial) ]**
160
+
161
+
162
+ ## Menu Grouping
163
+
164
+ By default **Rear** will build a "flat" menu.
165
+
166
+ To have some models displayed under some group, use `menu_group` method:
167
+
168
+ ```ruby
169
+ Rear.register City, Country, State do
170
+ menu_group :Location
171
+ # or
172
+ under :Location # `under` is an alias for `menu_group`
173
+ end
174
+ ```
175
+
176
+ This will create "Location" group that will display "City", "Country" and "State" links on hover.
177
+
178
+ **[ [contents &uarr;](https://github.com/espresso/rear#tutorial) ]**
179
+
180
+
181
+ ## ReadOnly Mode
182
+
183
+ When you need to display a whole model in readonly mode, use `readonly!` method:
184
+
185
+ ```ruby
186
+ class State < ActiveRecord::Base
187
+ # ...
188
+ end
189
+
190
+ Rear.register State do
191
+ readonly!
192
+ end
193
+ ```
194
+
195
+ In readonly mode items can not be created/updated nor deleted.
196
+
197
+ Also all associations will be in readonly mode.
198
+
199
+
200
+ **[ [contents &uarr;](https://github.com/espresso/rear#tutorial) ]**
201
+
data/lib/rear.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'el'
2
+ require 'slim'
3
+
4
+ require 'rear/constants'
5
+ require 'rear/utils'
6
+ require 'rear/orm'
7
+ require 'rear/actions'
8
+ require 'rear/input'
9
+ require 'rear/rear'
10
+ require 'rear/setup'
11
+ require 'rear/helpers'
12
+ require 'rear/controller_setup'
13
+ require 'rear/home_controller'
@@ -0,0 +1,98 @@
1
+ module RearActions
2
+ include RearConstants
3
+
4
+ def get_index
5
+ reander_l(:layout) { reander_p 'pane/layout' }
6
+ end
7
+
8
+ def get_quickview
9
+ reander_p 'pane/quickview'
10
+ end
11
+
12
+ def get_edit id
13
+ reander_l(:layout) { reander_p 'editor/layout' }
14
+ end
15
+
16
+ def get_bulk_edit
17
+ @items = params[:items].to_s.gsub(/[^\d|\s]/, '')
18
+ self.item, self.item_id = model.new, 0
19
+ reander_p 'editor/bulk_edit'
20
+ end
21
+
22
+ def post_bulk_edit
23
+ data = Hash[params]
24
+ (columns = data.delete('rear-bulk_editor-crudifier_toggler')).is_a?(Array) ||
25
+ halt(400, 'Nothing to update. Please edit at least one column.')
26
+ items = data.delete('rear-bulk_editor-items').to_s.strip.split
27
+ items.empty? && halt(400, 'No items selected')
28
+
29
+ data.reject! {|k,v| !columns.include?(k)}
30
+ updated, failed = [], []
31
+ items.each do |id|
32
+ status, h, body = invoke_via_put(:crud, id, data)
33
+ status == 200 ? updated << id : failed << ['%s Failed' % id, *body]
34
+ end
35
+ failed.any? && styled_halt(500, failed)
36
+ "Successfully Updated Items:\n%s" % updated.join(', ')
37
+ end
38
+
39
+ def get_reverse_assoc source_ctrl, assoc_type, assoc_name, item_id, attached = nil
40
+ reander_p 'pane/assocs'
41
+ end
42
+
43
+ def post_reverse_assoc source_ctrl, assoc_type, assoc_name, item_id, attached = nil
44
+ case @reverse_assoc.type
45
+ when :belongs_to, :has_one
46
+ @reverse_assoc.source_item.send '%s=' % @reverse_assoc.name, @reverse_assoc.target_item
47
+ when :has_many
48
+ @reverse_assoc.source_item.send(@reverse_assoc.name).each do |ti|
49
+ ti[pkey] == @reverse_assoc.target_item[pkey] && halt(400, "Relation already exists")
50
+ end
51
+ if sequel?
52
+ m = 'add_' << orm.singularize(@reverse_assoc.name)
53
+ @reverse_assoc.source_item.send m, @reverse_assoc.target_item
54
+ else
55
+ @reverse_assoc.source_item.send(@reverse_assoc.name) << @reverse_assoc.target_item
56
+ end
57
+ end
58
+ @reverse_assoc.source_item.save
59
+ end
60
+
61
+ def delete_reverse_assoc source_ctrl, assoc_type, assoc_name, item_id, attached = nil
62
+ case @reverse_assoc.type
63
+ when :belongs_to, :has_one
64
+ @reverse_assoc.source_item.send '%s=' % @reverse_assoc.name, nil
65
+ when :has_many
66
+ if sequel?
67
+ (@reverse_assoc.source_item.send(@reverse_assoc.name)||[]).each do |ti|
68
+ next unless ti[pkey] == @reverse_assoc.target_item[pkey]
69
+ m = 'remove_' << orm.singularize(@reverse_assoc.name)
70
+ @reverse_assoc.source_item.send m, ti
71
+ end
72
+ else
73
+ target_items = Array.new(@reverse_assoc.source_item.send(@reverse_assoc.name)||[]).reject do |ti|
74
+ ti[pkey] == @reverse_assoc.target_item[pkey]
75
+ end
76
+ @reverse_assoc.source_item.send '%s=' % @reverse_assoc.name, target_items
77
+ end
78
+ end
79
+ @reverse_assoc.source_item.save
80
+ end
81
+
82
+ def delete_delete_selected
83
+ halt(400, '%s is in readonly mode' % model) if readonly?
84
+ if (ids = params[:items].to_s.split.map(&:to_i).select {|r| r > 0}).any?
85
+ orm.delete_multiple(*ids)
86
+ end
87
+ end
88
+
89
+ def html_filters partial = false
90
+ partial ? render_filters : reander_p('filters/layout')
91
+ end
92
+
93
+ def get_assets(*)
94
+ env['PATH_INFO'] = env['PATH_INFO'].to_s.sub(ASSETS__SUFFIX_REGEXP, '')
95
+ send_files @__rear__assets_fullpath ? @__rear__assets_fullpath :
96
+ (@__rear__assets_path ? File.join(app.root, @__rear__assets_path) : ASSETS__PATH)
97
+ end
98
+ end
@@ -0,0 +1,61 @@
1
+ module RearConstants
2
+
3
+ PATH__TEMPLATES = (File.expand_path('../templates', __FILE__) + '/').freeze
4
+
5
+ ASSETS__PATH = (File.expand_path('../../../assets', __FILE__) + '/').freeze
6
+ ASSETS__SUFFIX = '-rear'.freeze
7
+ ASSETS__SUFFIX_REGEXP = /#{Regexp.escape ASSETS__SUFFIX}\Z/.freeze
8
+
9
+ COLUMNS__HANDLED_TYPES = [
10
+ :string, :text,
11
+ :date, :time, :datetime,
12
+ :radio, :checkbox, :select,
13
+ :boolean
14
+ ].freeze
15
+ COLUMNS__DEFAULT_TYPE = :string
16
+ COLUMNS__BOOLEAN_MAP = {true => 'Y', false => 'N'}.freeze
17
+ COLUMNS__PANE_MAX_LENGTH = 255
18
+
19
+ PAGER__SIDE_PAGES = 5
20
+
21
+ FILTERS__HANDLED_TYPES = COLUMNS__HANDLED_TYPES
22
+ FILTERS__DEFAULT_TYPE = :string
23
+ FILTERS__DECORATIVE_CMP = :decorative
24
+ FILTERS__STR_TO_BOOLEAN = {"true" => true, "false" => false}.freeze
25
+ FILTERS__QUERY_MAP = lambda do |orm|
26
+ # using lambda will always return a new copy of Hash, so no need to deep copy it
27
+ default_query_map = {
28
+ gt: ['%s > ?', '%s'],
29
+ lt: ['%s < ?', '%s'],
30
+ gte: ['%s >= ?', '%s'],
31
+ lte: ['%s <= ?', '%s'],
32
+ not: ['%s <> ?', '%s'],
33
+ eql: ['%s = ?', '%s'],
34
+
35
+ # use left and right wildcards - '%VALUE%'
36
+ like: ['%s LIKE ?', '%%%s%'],
37
+ unlike: ['%s NOT LIKE ?', '%%%s%'],
38
+
39
+ # use only left wildcard - exact match for end of line - LIKE '%VALUE'
40
+ _like: ['%s LIKE ?', '%%%s'],
41
+ _unlike: ['%s NOT LIKE ?', '%%%s'],
42
+
43
+ # use only right wildcard - exact match for beginning of line - LIKE 'VALUE%'
44
+ like_: ['%s LIKE ?', '%s%'],
45
+ unlike_: ['%s NOT LIKE ?', '%s%'],
46
+
47
+ in: ['%s IN ?'],
48
+ csl: ['%s IN ?'], # comma separated list
49
+ FILTERS__DECORATIVE_CMP => []
50
+ }
51
+ {
52
+ ar: default_query_map.merge(:in => ['%s IN (?)'], :csl => ['%s IN (?)']),
53
+ dm: default_query_map,
54
+ sq: default_query_map,
55
+ }[orm]
56
+ end
57
+
58
+ ASSOCS__STRUCT = lambda do
59
+ {belongs_to: {}, has_one: {}, has_many: {}}
60
+ end
61
+ end
@@ -0,0 +1,249 @@
1
+ class RearControllerSetup
2
+ include RearConstants
3
+
4
+ class << self
5
+
6
+ def init(*args, &proc)
7
+ new.init(*args, &proc)
8
+ end
9
+
10
+ def crudify(*args, &proc)
11
+ new.crudify(*args, &proc)
12
+ end
13
+
14
+ end
15
+
16
+ def init ctrl
17
+ (@ctrl = ctrl).class_exec do
18
+ extend RearSetup
19
+ extend RearHelpers::ClassMixin
20
+ include RearConstants
21
+ include RearHelpers::InstanceMixin
22
+ import RearActions
23
+
24
+ include EL::Ace if defined?(EL::Ace)
25
+ include EL::CKE if defined?(EL::CKE)
26
+
27
+ reject_automount!
28
+ EUtils.register_extra_engines!
29
+ end
30
+
31
+ define_setup_methods
32
+ define_error_handlers
33
+ end
34
+
35
+ def crudify ctrl, model, opts, &proc
36
+ (@ctrl = ctrl).class_exec do
37
+ @__rear__orm = RearUtils.orm(model)
38
+ @__rear__assocs = RearUtils.extract_assocs(model)
39
+ @__rear__real_columns, @__rear__pkey = RearUtils.extract_columns(model)
40
+ @__rear__real_columns.each do |c|
41
+ input(*c) unless @__rear__assocs[:belongs_to].any? {|a,s|
42
+ s[:belongs_to_keys][:source] == c.first
43
+ }
44
+ end
45
+ end
46
+
47
+ setup_crudifier(opts, &proc)
48
+ define_association_hooks
49
+ define_pane_hooks
50
+ define_editor_hooks
51
+ define_default_filters
52
+ end
53
+
54
+ def setup_crudifier opts, &proc
55
+ @ctrl.class_exec do
56
+
57
+ # use :destroy! on DataMapper
58
+ opts[:delete] ||= :destroy! if orm == :dm
59
+
60
+ # asking Espresso to define CRUD actions
61
+ crudify(model, :crud, opts, &proc)
62
+
63
+ alias_before :post_crud, :create, :save
64
+ alias_before :put_crud, :update, :save
65
+ alias_before :delete_crud, :destroy
66
+
67
+ before :create, :update, :destroy do
68
+ halt(400, '%s is in readonly mode' % model) if readonly?
69
+ end
70
+ end
71
+ end
72
+
73
+ def define_default_filters
74
+ @ctrl.class_exec do
75
+ # built-in filter for all controllers. it will search by primary key
76
+ filter(pkey, cmp: :csl, class: 'input-small search-query')
77
+ end
78
+ end
79
+
80
+ def define_setup_methods
81
+ @ctrl.class_exec do
82
+
83
+ # allow to set path to custom templates.
84
+ # to use custom templates install them via "$ rear i:t PATH"
85
+ # and use `rear_templates PATH` when mounting Rear controllers.
86
+ #
87
+ # @example
88
+ # # install templates in views/ folder
89
+ # $ rear i:t views/
90
+ #
91
+ # # set path to custom templates when mounting Rear controllers
92
+ # E.new do
93
+ # mount Rear.controllers, '/admin' do
94
+ # rear_templates 'views/rear/'
95
+ # end
96
+ # end
97
+ #
98
+ def rear_templates path
99
+ return @__rear__templates_fullpath = path.to_s if path =~ /\A(\w\:)?\// && @__rear__templates_fullpath.nil?
100
+ @__rear__templates_path = path.to_s if @__rear__templates_path.nil?
101
+ end
102
+ define_setup_method :rear_templates
103
+
104
+ # similar to `rear_templates` except it sets path to custom assets
105
+ def rear_assets path
106
+ return @__rear__assets_fullpath = path.to_s if path =~ /\A(\w\:)?\// && @__rear__assets_fullpath.nil?
107
+ @__rear__assets_path = path.to_s if @__rear__assets_path.nil?
108
+ end
109
+ define_setup_method :rear_assets
110
+
111
+ end
112
+ end
113
+
114
+ def define_error_handlers
115
+ @ctrl.class_exec do
116
+ error 500 do |e|
117
+ error = if e.respond_to?(:backtrace)
118
+ e.backtrace.inject([e.message]) {|bt,l| bt << l}
119
+ elsif e.is_a?(Array)
120
+ e
121
+ else
122
+ [e.to_s]
123
+ end
124
+ out = rq.xhr? ? error*"\n" : reander_l(:layout) {reander_p :error, error: error}
125
+ halt 500, out
126
+ end
127
+ end
128
+ end
129
+
130
+ def define_association_hooks
131
+ @ctrl.class_exec do
132
+ # all communications between associated models happens through primary keys.
133
+ # it does not matter what keys models using to associate one to each other,
134
+ # Rear will always use pkeys to display/update associated objects.
135
+ # this is achieved by using ORM setters for associated objects,
136
+ # like `state=` on `belongs_to :state` assocs
137
+ # or `cities<<` on `has_many :cities` etc.
138
+ # so it will basically fetch remote objects by pkey and feed them to that setters.
139
+ #
140
+ # hooks order is important. this one should always go first
141
+ before /reverse_assoc/ do
142
+ ctrl = action_params[:source_ctrl].split('::').inject(Object) {|f,c| f.const_get(c)}
143
+ assoc = ctrl.assocs[action_params[:assoc_type].to_sym][action_params[:assoc_name].to_sym]
144
+ readonly = ctrl.readonly_assocs.include?(assoc[:name]) || assoc[:readonly] || ctrl.readonly?
145
+ halt(400, '%s is in readonly mode' % model) if readonly && (post? || delete?)
146
+ struct = {
147
+ type: assoc[:type],
148
+ name: assoc[:name],
149
+ dom_id: assoc[:dom_id] + (action_params[:attached] ? '' : '_detached'),
150
+ readonly: readonly ? true : false, # using true/false here cause it will be converted to string and fed to javascript
151
+ attached: action_params[:attached],
152
+ route: route(action, *action_params__array[0..3]),
153
+ source_item: RearORM.new(ctrl.model, ctrl.pkey)[action_params[:item_id].to_i],
154
+ target_item: orm[params[:target_item_id].to_i]||{},
155
+ source_key: nil,
156
+ target_key: nil,
157
+ }
158
+ if struct[:type] == :belongs_to && (struct[:attached] || struct[:source_item].nil?)
159
+ struct[:source_key], struct[:target_key] =
160
+ assoc[:belongs_to_keys].values_at(:source, :target)
161
+ end
162
+ @reverse_assoc = Struct.new(*struct.keys.map(&:to_sym)).new(*struct.values).freeze
163
+ end
164
+ end
165
+ end
166
+
167
+ def define_pane_hooks
168
+ @ctrl.class_exec do
169
+ before :get_index, :get_reverse_assoc, :get_quickview do
170
+
171
+ conditions = filters_to_sql
172
+
173
+ source_item, source_assoc = nil
174
+ if @reverse_assoc && @reverse_assoc.attached
175
+ if source_item = @reverse_assoc.source_item
176
+ source_assoc = @reverse_assoc.name
177
+ else
178
+ conditions = {conditions: {pkey => 0}}
179
+ end
180
+ end
181
+
182
+ total_items = source_item ?
183
+ orm.assoc_count(source_assoc, source_item, conditions) :
184
+ orm.count(conditions)
185
+
186
+ total_pages = (total_items.to_f / __rear__.ipp.to_f).ceil
187
+ side_pages = xhr? ? 2 : PAGER__SIDE_PAGES
188
+
189
+ current_page = params[:page].to_i
190
+ current_page = 1 if current_page < 1
191
+ current_page = total_pages if current_page > total_pages
192
+
193
+ page_min = current_page - side_pages
194
+ page_min = total_pages - (side_pages * 2) if (current_page + side_pages) > total_pages
195
+ page_min = 1 if page_min < 1
196
+
197
+ page_max = current_page + side_pages
198
+ page_max = side_pages * 2 if current_page < side_pages
199
+ page_max = total_pages if page_max > total_pages
200
+
201
+ offset = (current_page - 1) * __rear__.ipp
202
+ offset = 0 if offset < 0
203
+
204
+ counter = [offset + 1, offset + __rear__.ipp, total_items]
205
+ counter[0] = offset if counter[0] > total_items
206
+ counter[1] = total_items if counter[1] > total_items
207
+
208
+ @pager_context = {
209
+ total_items: total_items,
210
+ total_pages: total_pages,
211
+ current_page: current_page,
212
+ page_min: page_min,
213
+ page_max: page_max,
214
+ counter: counter.map {|n| RearUtils.number_with_delimiter(n)}
215
+ }
216
+ @pager = total_pages > 1 ? reander_p(:pager, @pager_context) : ''
217
+
218
+ conditions[:limit ] = __rear__.ipp
219
+ conditions[:offset] = offset
220
+
221
+ if order = order_params_to_sql || __rear__.order_by
222
+ conditions[:order] = order
223
+ end
224
+
225
+ @items = if source_item
226
+ orm.assoc_filter(source_assoc, source_item, conditions)
227
+ else
228
+ orm.filter(conditions)
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ def define_editor_hooks
235
+ @ctrl.class_exec do
236
+ before :get_edit do
237
+ if (id = action_params[:id].to_i) > 0
238
+ @item = orm[id] || halt(400, "Item with ID %s not found" % id)
239
+ @item_id = id
240
+ else
241
+ @item = @brand_new_item = model.new
242
+ @item_id = 0
243
+ end
244
+ end
245
+
246
+ end
247
+ end
248
+
249
+ end