rear 0.2.0

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.
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