trestle 0.8.4 → 0.8.11

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 (164) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -0
  3. data/.travis.yml +14 -1
  4. data/Gemfile +6 -0
  5. data/README.md +15 -9
  6. data/app/assets/javascripts/trestle/admin.js +11 -10
  7. data/app/assets/javascripts/trestle/components/_confirmation.js +40 -0
  8. data/app/assets/javascripts/trestle/components/_datepicker.js +37 -0
  9. data/app/assets/javascripts/trestle/components/_dialog.js +136 -0
  10. data/app/assets/javascripts/trestle/{_errors.js → components/_errors.js} +2 -2
  11. data/app/assets/javascripts/trestle/components/_form.js +75 -0
  12. data/app/assets/javascripts/trestle/{_gallery.js → components/_gallery.js} +3 -3
  13. data/app/assets/javascripts/trestle/{_select.js → components/_select.js} +2 -2
  14. data/app/assets/javascripts/trestle/components/_sidebar.js +77 -0
  15. data/app/assets/javascripts/trestle/{_table.js → components/_table.js} +1 -1
  16. data/app/assets/javascripts/trestle/components/_tabs.js +25 -0
  17. data/app/assets/javascripts/trestle/components/_tooltips.js +19 -0
  18. data/app/assets/javascripts/trestle/core/_contexts.js +13 -0
  19. data/app/assets/javascripts/trestle/core/_cookies.js +24 -0
  20. data/app/assets/javascripts/trestle/core/_events.js +39 -0
  21. data/app/assets/javascripts/trestle/core/_l10n.js +23 -0
  22. data/app/assets/javascripts/trestle/core/_visit.js +10 -0
  23. data/app/assets/stylesheets/trestle/components/_alerts.scss +28 -0
  24. data/app/assets/stylesheets/trestle/components/_avatar.scss +23 -1
  25. data/app/assets/stylesheets/trestle/components/_breadcrumbs.scss +12 -0
  26. data/app/assets/stylesheets/trestle/components/_buttons.scss +111 -1
  27. data/app/assets/stylesheets/trestle/components/_content.scss +53 -21
  28. data/app/assets/stylesheets/trestle/components/_datepicker.scss +28 -0
  29. data/app/assets/stylesheets/trestle/components/_fields.scss +17 -0
  30. data/app/assets/stylesheets/trestle/components/_input-group.scss +26 -1
  31. data/app/assets/stylesheets/trestle/components/_modal.scss +108 -0
  32. data/app/assets/stylesheets/trestle/components/_navigation.scss +104 -14
  33. data/app/assets/stylesheets/trestle/components/_pagination.scss +3 -3
  34. data/app/assets/stylesheets/trestle/components/_scopes.scss +11 -2
  35. data/app/assets/stylesheets/trestle/components/_sidebar.scss +3 -2
  36. data/app/assets/stylesheets/trestle/components/_table.scss +34 -10
  37. data/app/assets/stylesheets/trestle/components/_tags.scss +9 -0
  38. data/app/assets/stylesheets/trestle/components/_timestamp.scss +4 -1
  39. data/app/assets/stylesheets/trestle/components/_toolbars.scss +55 -0
  40. data/app/assets/stylesheets/trestle/components/_wells.scss +9 -1
  41. data/app/assets/stylesheets/trestle/core/_defaults.scss +21 -6
  42. data/app/assets/stylesheets/trestle/core/_layout.scss +8 -0
  43. data/app/assets/stylesheets/trestle/core/_mixins.scss +11 -0
  44. data/app/assets/stylesheets/trestle/core/_typography.scss +39 -0
  45. data/app/controllers/concerns/trestle/controller/breadcrumbs.rb +21 -0
  46. data/app/controllers/concerns/trestle/controller/callbacks.rb +21 -0
  47. data/app/controllers/concerns/trestle/controller/dialog.rb +16 -0
  48. data/app/controllers/concerns/trestle/controller/helpers.rb +18 -0
  49. data/app/controllers/concerns/trestle/controller/layout.rb +16 -0
  50. data/app/controllers/concerns/trestle/controller/location.rb +15 -0
  51. data/app/controllers/trestle/application_controller.rb +6 -30
  52. data/app/helpers/trestle/avatar_helper.rb +9 -2
  53. data/app/helpers/trestle/flash_helper.rb +15 -0
  54. data/app/helpers/trestle/form_helper.rb +8 -5
  55. data/app/helpers/trestle/format_helper.rb +7 -3
  56. data/app/helpers/trestle/headings_helper.rb +27 -0
  57. data/app/helpers/trestle/hook_helper.rb +13 -4
  58. data/app/helpers/trestle/i18n_helper.rb +14 -0
  59. data/app/helpers/trestle/panel_helper.rb +24 -0
  60. data/app/helpers/trestle/tab_helper.rb +2 -2
  61. data/app/helpers/trestle/table_helper.rb +41 -2
  62. data/app/helpers/trestle/timestamp_helper.rb +49 -7
  63. data/app/helpers/trestle/toolbars_helper.rb +34 -0
  64. data/app/helpers/trestle/url_helper.rb +72 -8
  65. data/app/views/layouts/trestle/admin.html.erb +3 -5
  66. data/app/views/trestle/application/_dialog.html.erb +34 -0
  67. data/app/views/trestle/application/_header.html.erb +22 -20
  68. data/app/views/trestle/application/_layout.html.erb +1 -1
  69. data/app/views/trestle/application/_tabs.html.erb +8 -2
  70. data/app/views/trestle/{application → flash}/_alert.html.erb +7 -2
  71. data/app/views/trestle/flash/_debug.html.erb +8 -0
  72. data/app/views/trestle/flash/_flash.html.erb +7 -0
  73. data/app/views/trestle/resource/_scopes.html.erb +1 -1
  74. data/app/views/trestle/resource/edit.html.erb +8 -10
  75. data/app/views/trestle/resource/index.html.erb +16 -4
  76. data/app/views/trestle/resource/new.html.erb +6 -6
  77. data/app/views/trestle/resource/show.html.erb +8 -10
  78. data/app/views/trestle/shared/_sidebar.html.erb +10 -7
  79. data/app/views/trestle/shared/_title.html.erb +14 -0
  80. data/app/views/trestle/table/_table.html.erb +4 -6
  81. data/bower.json +1 -1
  82. data/config/locales/cs.rb +18 -0
  83. data/config/locales/cs.yml +95 -0
  84. data/config/locales/en.yml +65 -36
  85. data/config/locales/es-MX.yml +94 -0
  86. data/config/locales/es.yml +94 -0
  87. data/config/locales/fr.rb +18 -0
  88. data/config/locales/fr.yml +84 -0
  89. data/config/locales/lv.rb +18 -0
  90. data/config/locales/lv.yml +94 -0
  91. data/config/locales/nl.rb +18 -0
  92. data/config/locales/nl.yml +82 -0
  93. data/config/locales/pl.rb +18 -0
  94. data/config/locales/pl.yml +85 -0
  95. data/config/locales/pt-BR.rb +18 -0
  96. data/config/locales/pt-BR.yml +84 -0
  97. data/config/locales/zh-CN.rb +18 -0
  98. data/config/locales/zh-CN.yml +94 -0
  99. data/gemfiles/rails-4.2.gemfile +16 -0
  100. data/gemfiles/rails-5.0.gemfile +16 -0
  101. data/gemfiles/rails-5.1.gemfile +16 -0
  102. data/gemfiles/rails-5.2.gemfile +16 -0
  103. data/gemfiles/rails-edge.gemfile +17 -0
  104. data/lib/generators/trestle/install/templates/trestle.rb.erb +18 -2
  105. data/lib/generators/trestle/resource/templates/admin.rb.erb +2 -2
  106. data/lib/trestle/adapters/active_record_adapter.rb +2 -6
  107. data/lib/trestle/adapters/adapter.rb +40 -15
  108. data/lib/trestle/adapters/sequel_adapter.rb +2 -6
  109. data/lib/trestle/admin/builder.rb +42 -12
  110. data/lib/trestle/admin/controller.rb +3 -1
  111. data/lib/trestle/admin.rb +84 -3
  112. data/lib/trestle/breadcrumb.rb +16 -1
  113. data/lib/trestle/configurable.rb +6 -0
  114. data/lib/trestle/configuration.rb +28 -5
  115. data/lib/trestle/display.rb +1 -1
  116. data/lib/trestle/engine.rb +10 -4
  117. data/lib/trestle/form/automatic.rb +6 -3
  118. data/lib/trestle/form/builder.rb +5 -1
  119. data/lib/trestle/form/field.rb +1 -1
  120. data/lib/trestle/form/fields/date_picker.rb +2 -2
  121. data/lib/trestle/form/fields/form_control.rb +14 -4
  122. data/lib/trestle/form/fields/form_group.rb +14 -3
  123. data/lib/trestle/form/fields/select.rb +5 -1
  124. data/lib/trestle/form/fields/tag_select.rb +1 -1
  125. data/lib/trestle/form/renderer.rb +2 -2
  126. data/lib/trestle/form.rb +7 -3
  127. data/lib/trestle/hook.rb +27 -0
  128. data/lib/trestle/model_name.rb +64 -0
  129. data/lib/trestle/navigation/group.rb +16 -0
  130. data/lib/trestle/navigation/item.rb +11 -1
  131. data/lib/trestle/navigation.rb +24 -6
  132. data/lib/trestle/options.rb +7 -5
  133. data/lib/trestle/reloader.rb +1 -1
  134. data/lib/trestle/resource/adapter_methods.rb +62 -0
  135. data/lib/trestle/resource/builder.rb +43 -17
  136. data/lib/trestle/resource/collection.rb +68 -0
  137. data/lib/trestle/resource/controller.rb +70 -33
  138. data/lib/trestle/resource.rb +97 -105
  139. data/lib/trestle/scope.rb +13 -3
  140. data/lib/trestle/tab.rb +4 -0
  141. data/lib/trestle/table/actions_column.rb +41 -31
  142. data/lib/trestle/table/automatic.rb +1 -2
  143. data/lib/trestle/table/builder.rb +2 -2
  144. data/lib/trestle/table/column.rb +40 -9
  145. data/lib/trestle/table/row.rb +18 -2
  146. data/lib/trestle/table.rb +18 -2
  147. data/lib/trestle/toolbar/builder.rb +52 -0
  148. data/lib/trestle/toolbar/context.rb +39 -0
  149. data/lib/trestle/toolbar.rb +43 -0
  150. data/lib/trestle/version.rb +1 -1
  151. data/lib/trestle.rb +9 -4
  152. data/trestle.gemspec +5 -3
  153. data/vendor/assets/bower_components/trestle/select2/dist/js/select2.full.js +90 -69
  154. data/vendor/assets/javascripts/trestle/flatpickr.js.erb +2 -0
  155. data/vendor/assets/stylesheets/trestle/magnific-popup.scss +13 -1
  156. metadata +99 -22
  157. data/app/assets/javascripts/trestle/_confirmation.js +0 -23
  158. data/app/assets/javascripts/trestle/_datepicker.js +0 -22
  159. data/app/assets/javascripts/trestle/_form.js +0 -6
  160. data/app/assets/javascripts/trestle/_sidebar.js +0 -52
  161. data/app/assets/javascripts/trestle/_tabs.js +0 -13
  162. data/app/assets/javascripts/trestle/_tooltips.js +0 -3
  163. data/app/helpers/trestle/title_helper.rb +0 -26
  164. data/app/views/trestle/application/_flash.html.erb +0 -11
@@ -13,15 +13,23 @@ module Trestle
13
13
  def input_group
14
14
  if @prepend || @append
15
15
  content_tag(:div, class: "input-group") do
16
- concat content_tag(:span, @prepend, class: "input-group-addon") if @prepend
16
+ concat input_group_addon(@prepend) if @prepend
17
17
  concat yield
18
- concat content_tag(:span, @append, class: "input-group-addon") if @append
18
+ concat input_group_addon(@append) if @append
19
19
  end
20
20
  else
21
21
  yield
22
22
  end
23
23
  end
24
24
 
25
+ def input_group_addon(addon)
26
+ if addon[:wrap]
27
+ content_tag(:span, addon[:content], class: "input-group-addon")
28
+ else
29
+ addon[:content]
30
+ end
31
+ end
32
+
25
33
  def defaults
26
34
  super.merge(class: ["form-control"])
27
35
  end
@@ -29,8 +37,10 @@ module Trestle
29
37
  def extract_options!
30
38
  super
31
39
 
32
- @prepend = options.delete(:prepend)
33
- @append = options.delete(:append)
40
+ @prepend = { content: options.delete(:prepend), wrap: true } if options[:prepend]
41
+ @prepend = { content: options.delete(:prepend!), wrap: false } if options[:prepend!]
42
+ @append = { content: options.delete(:append), wrap: true } if options[:append]
43
+ @append = { content: options.delete(:append!), wrap: false } if options[:append!]
34
44
  end
35
45
  end
36
46
  end
@@ -2,19 +2,30 @@ module Trestle
2
2
  class Form
3
3
  module Fields
4
4
  class FormGroup < Field
5
+ WRAPPER_OPTIONS = [:help, :label, :hide_label]
6
+
5
7
  def render
6
8
  options[:class] << 'has-error' if errors.any?
7
9
 
8
- content_tag(:div, options) do
10
+ content_tag(:div, options.except(*WRAPPER_OPTIONS)) do
9
11
  concat label unless options[:label] == false
10
- concat block.call if block
12
+ concat template.capture(&block) if block
11
13
  concat help_message if options[:help]
12
14
  concat error_message if errors.any?
13
15
  end
14
16
  end
15
17
 
16
18
  def help_message
17
- content_tag(:p, options[:help], class: "help-block")
19
+ classes = ["help-block"]
20
+
21
+ if options[:help].is_a?(Hash)
22
+ message = options[:help][:text]
23
+ classes << "floating" if options[:help][:float]
24
+ else
25
+ message = options[:help]
26
+ end
27
+
28
+ content_tag(:p, message, class: classes)
18
29
  end
19
30
 
20
31
  def error_message
@@ -7,7 +7,7 @@ module Trestle
7
7
  def initialize(builder, template, name, choices=nil, options={}, html_options={}, &block)
8
8
  super(builder, template, name, options, &block)
9
9
 
10
- @choices = Choices.new(choices || builder.object.send(name))
10
+ @choices = Choices.new(choices || default_choices)
11
11
  @html_options = default_html_options.merge(html_options)
12
12
  end
13
13
 
@@ -19,6 +19,10 @@ module Trestle
19
19
  Trestle::Options.new(class: ["form-control"], data: { enable_select2: true })
20
20
  end
21
21
 
22
+ def default_choices
23
+ builder.object.send(name) if builder.object
24
+ end
25
+
22
26
  # Allows an array of model instances (or a scope) to be
23
27
  # passed to the select field as the list of choices.
24
28
  class Choices
@@ -7,7 +7,7 @@ module Trestle
7
7
  end
8
8
 
9
9
  def default_html_options
10
- super.merge(multiple: true, class: "tag-select", data: { tags: true, select_on_close: true })
10
+ super.merge(multiple: true, class: "tag-select", data: { tags: true })
11
11
  end
12
12
  end
13
13
  end
@@ -5,11 +5,11 @@ module Trestle
5
5
  include ::ActionView::Helpers::CaptureHelper
6
6
 
7
7
  # Whitelisted helpers will concatenate their result to the output buffer when called.
8
- WHITELISTED_HELPERS = [:row, :col, :render, :tab, :toolbar, :table, :divider]
8
+ WHITELISTED_HELPERS = [:row, :col, :render, :tab, :table, :divider, :h1, :h2, :h3, :h4, :h5, :h6, :panel, :well]
9
9
 
10
10
  # Raw block helpers will pass their block argument directly to the method
11
11
  # without wrapping it in a new output buffer.
12
- RAW_BLOCK_HELPERS = [:table]
12
+ RAW_BLOCK_HELPERS = [:table, :toolbar]
13
13
 
14
14
  # The #select and #display methods are defined on Kernel. Undefine them so
15
15
  # that they can be delegated to the form builder or template by method_missing.
data/lib/trestle/form.rb CHANGED
@@ -8,10 +8,14 @@ module Trestle
8
8
  autoload :Fields
9
9
  autoload :Renderer
10
10
 
11
- attr_reader :block
11
+ attr_reader :options, :block
12
12
 
13
- def initialize(&block)
14
- @block = block
13
+ def initialize(options={}, &block)
14
+ @options, @block = options, block
15
+ end
16
+
17
+ def dialog?
18
+ options[:dialog] == true
15
19
  end
16
20
 
17
21
  def render(template, instance)
@@ -0,0 +1,27 @@
1
+ module Trestle
2
+ class Hook
3
+ attr_reader :name, :options, :block
4
+
5
+ def initialize(name, options={}, &block)
6
+ @name, @options, @block = name, options, block
7
+ end
8
+
9
+ def ==(other)
10
+ other.is_a?(self.class) && name == other.name && options == other.options && block == other.block
11
+ end
12
+
13
+ def visible?(context)
14
+ if options[:if]
15
+ context.instance_exec(&options[:if])
16
+ elsif options[:unless]
17
+ !context.instance_exec(&options[:unless])
18
+ else
19
+ true
20
+ end
21
+ end
22
+
23
+ def evaluate(context)
24
+ context.instance_exec(&block)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ require "active_model/naming"
2
+
3
+ module Trestle
4
+ class ModelName
5
+ attr_reader :klass
6
+
7
+ delegate :downcase, :upcase, :titleize, :titlecase, to: :to_s
8
+
9
+ def initialize(klass)
10
+ @klass = klass
11
+ @name = klass.respond_to?(:model_name) ? klass.model_name : ActiveModel::Name.new(klass)
12
+ end
13
+
14
+ def ==(other)
15
+ other.is_a?(self.class) && klass == other.klass
16
+ end
17
+
18
+ def to_s
19
+ singular
20
+ end
21
+
22
+ def singular(options={})
23
+ human(default_singular, options)
24
+ end
25
+ alias_method :singularize, :singular
26
+
27
+ def plural(options={})
28
+ if i18n_supported? && i18n_pluralizations_available?
29
+ human(default_plural, { count: :many }.merge(options))
30
+ else
31
+ default_plural
32
+ end
33
+ end
34
+ alias_method :pluralize, :plural
35
+
36
+ protected
37
+ # Default singular version if it cannot be determined from i18n
38
+ def default_singular
39
+ @name.name.demodulize.titleize
40
+ end
41
+
42
+ # Default plural version if it cannot be determined from i18n
43
+ def default_plural
44
+ singular.pluralize(I18n.locale)
45
+ end
46
+
47
+ # Safely delegates to ActiveModel::Name#human, catching exceptions caused by missing pluralizations
48
+ def human(default, options={})
49
+ @name.human(options.merge(default: default))
50
+ rescue I18n::InvalidPluralizationData
51
+ default
52
+ end
53
+
54
+ # Checks if the model can be translated by ActiveModel
55
+ def i18n_supported?
56
+ klass.respond_to?(:lookup_ancestors) && klass.respond_to?(:i18n_scope)
57
+ end
58
+
59
+ # Checks if multiple pluralization forms (e.g. zero/one/few/many/other) are available from i18n
60
+ def i18n_pluralizations_available?
61
+ @name.human(count: nil).is_a?(Hash)
62
+ end
63
+ end
64
+ end
@@ -26,6 +26,10 @@ module Trestle
26
26
  end
27
27
  end
28
28
 
29
+ def merge(other)
30
+ self.class.new(name, options.merge(other.options))
31
+ end
32
+
29
33
  def priority
30
34
  case options[:priority]
31
35
  when :first
@@ -40,6 +44,10 @@ module Trestle
40
44
  def label
41
45
  I18n.t("admin.navigation.groups.#{name}", default: name.to_s.titlecase)
42
46
  end
47
+
48
+ def id
49
+ name.to_s.parameterize
50
+ end
43
51
  end
44
52
 
45
53
  class NullGroup
@@ -59,9 +67,17 @@ module Trestle
59
67
  NullGroup.hash
60
68
  end
61
69
 
70
+ def id
71
+ nil
72
+ end
73
+
62
74
  def <=>(other)
63
75
  -1
64
76
  end
77
+
78
+ def merge(other)
79
+ self
80
+ end
65
81
  end
66
82
  end
67
83
  end
@@ -40,7 +40,7 @@ module Trestle
40
40
  end
41
41
 
42
42
  def label
43
- I18n.t("admin.navigation.items.#{name}", default: name.to_s.titlecase)
43
+ options[:label] || I18n.t("admin.navigation.items.#{name}", default: name.to_s.titlecase)
44
44
  end
45
45
 
46
46
  def icon
@@ -55,6 +55,16 @@ module Trestle
55
55
  Badge.new(options[:badge]) if badge?
56
56
  end
57
57
 
58
+ def visible?(context)
59
+ if options[:if]
60
+ context.instance_exec(&options[:if])
61
+ elsif options[:unless]
62
+ !context.instance_exec(&options[:unless])
63
+ else
64
+ true
65
+ end
66
+ end
67
+
58
68
  class Badge
59
69
  attr_reader :text
60
70
 
@@ -7,16 +7,17 @@ module Trestle
7
7
  autoload :Group
8
8
  autoload :NullGroup, "trestle/navigation/group"
9
9
 
10
- def initialize(blocks)
11
- @blocks = blocks
12
- end
10
+ attr_reader :items
13
11
 
14
- def items
15
- @blocks.map(&:items).flatten
12
+ def initialize(items)
13
+ @items = items
16
14
  end
17
15
 
18
16
  def by_group
19
- Hash[stable_sort(items.group_by(&:group)).map { |group, items| [group, stable_sort(items)] }]
17
+ sorted_groups = stable_sort(items.group_by { |item| groups[item.group.id] })
18
+ sorted_items = sorted_groups.map { |group, items| [group, stable_sort(items)] }
19
+
20
+ Hash[sorted_items]
20
21
  end
21
22
 
22
23
  def each(&block)
@@ -28,9 +29,26 @@ module Trestle
28
29
  sorted.first.first if sorted.any?
29
30
  end
30
31
 
32
+ def visible(context)
33
+ self.class.new(items.select { |item| item.visible?(context) })
34
+ end
35
+
36
+ def self.build(blocks)
37
+ new(blocks.map(&:items).flatten)
38
+ end
39
+
31
40
  private
32
41
  def stable_sort(items)
33
42
  items.sort_by.with_index { |item, i| [item, i] }
34
43
  end
44
+
45
+ def groups
46
+ @groups ||= items.inject({}) { |groups, item|
47
+ group = groups[item.group.id]
48
+
49
+ groups[item.group.id] = group ? group.merge(item.group) : item.group
50
+ groups
51
+ }
52
+ end
35
53
  end
36
54
  end
@@ -4,13 +4,15 @@ module Trestle
4
4
  self[hash]
5
5
  end
6
6
 
7
- def merge(other)
8
- dup.merge!(other)
7
+ def merge(other, &block)
8
+ dup.merge!(other, &block)
9
9
  end
10
10
 
11
- def merge!(other)
12
- deep_merge!(other || {}) do |key, v1, v2|
13
- if v1.is_a?(Array)
11
+ def merge!(other, &block)
12
+ super(other || {}) do |key, v1, v2|
13
+ if v1.is_a?(Hash) && v2.is_a?(Hash)
14
+ v1.merge(v2, &block)
15
+ elsif v1.is_a?(Array)
14
16
  v1 + Array(v2)
15
17
  else
16
18
  v2
@@ -34,7 +34,7 @@ module Trestle
34
34
  end
35
35
 
36
36
  def load_paths
37
- ActiveSupport::Dependencies.autoload_paths.grep(/\/app\/admin\Z/)
37
+ Trestle.config.load_paths.map { |path| path.respond_to?(:call) ? path.call : path }.flatten.map(&:to_s)
38
38
  end
39
39
 
40
40
  private
@@ -0,0 +1,62 @@
1
+ module Trestle
2
+ class Resource
3
+ module AdapterMethods
4
+ extend ActiveSupport::Concern
5
+
6
+ # Adapter instance bound to the current resource's context.
7
+ def adapter
8
+ @adapter ||= adapter_class.new(self, @context)
9
+ end
10
+
11
+ module ClassMethods
12
+ # Declares a method that is handled by the admin's adapter class.
13
+ def adapter_method(name)
14
+ delegate name, to: :adapter
15
+
16
+ singleton_class.class_eval do
17
+ delegate name, to: :adapter
18
+ end
19
+ end
20
+
21
+ # Defines an admin-specific adapter method.
22
+ #
23
+ # The given block is wrapped rather than passed to #define_method directly, so that
24
+ # adapter methods can be defined with incomplete block parameters. Unfortunately
25
+ # this means we lose the ability to call super from within a custom adapter method.
26
+ def define_adapter_method(name, &block)
27
+ return unless block_given?
28
+
29
+ adapter_methods.module_eval do
30
+ define_method(name) do |*args|
31
+ instance_exec(*args, &block)
32
+ end
33
+ end
34
+ end
35
+
36
+ # Returns the adapter class for this admin.
37
+ #
38
+ # Defaults to a subclass of `Trestle.config.default_adapter` with
39
+ # the admin-specific adapter methods module included.
40
+ def adapter_class
41
+ @adapter_class ||= Class.new(Trestle.config.default_adapter).include(adapter_methods)
42
+ end
43
+
44
+ # Sets an explicit adapter class for this admin.
45
+ # A subclass is created with the admin-specific adapter methods module included.
46
+ def adapter_class=(klass)
47
+ @adapter_class = Class.new(klass).include(adapter_methods)
48
+ end
49
+
50
+ # Module container for admin-specific adapter methods.
51
+ def adapter_methods
52
+ @adapter_methods ||= Module.new
53
+ end
54
+
55
+ # Unbound instance of adapter.
56
+ def adapter
57
+ @adapter ||= adapter_class.new(self)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -5,46 +5,53 @@ module Trestle
5
5
  self.controller = Controller
6
6
 
7
7
  def adapter(&block)
8
- klass = admin.adapter
9
- klass.instance_eval(&block) if block_given?
8
+ klass = admin.adapter_class
9
+ klass.class_eval(&block) if block_given?
10
10
  klass
11
11
  end
12
12
 
13
13
  def adapter=(adapter)
14
- admin.adapter = adapter
14
+ admin.adapter_class = adapter
15
+ end
16
+
17
+ def remove_action(*actions)
18
+ actions.each do |action|
19
+ controller.remove_possible_method(action.to_sym)
20
+ admin.actions.delete(action.to_sym)
21
+ end
15
22
  end
16
23
 
17
24
  def collection(&block)
18
- admin.collection = block
25
+ admin.define_adapter_method(:collection, &block)
19
26
  end
20
27
 
21
28
  def find_instance(&block)
22
- admin.find_instance = block
29
+ admin.define_adapter_method(:find_instance, &block)
23
30
  end
24
31
  alias instance find_instance
25
32
 
26
33
  def build_instance(&block)
27
- admin.build_instance = block
34
+ admin.define_adapter_method(:build_instance, &block)
28
35
  end
29
36
 
30
37
  def update_instance(&block)
31
- admin.update_instance = block
38
+ admin.define_adapter_method(:update_instance, &block)
32
39
  end
33
40
 
34
41
  def save_instance(&block)
35
- admin.save_instance = block
42
+ admin.define_adapter_method(:save_instance, &block)
36
43
  end
37
44
 
38
45
  def delete_instance(&block)
39
- admin.delete_instance = block
46
+ admin.define_adapter_method(:delete_instance, &block)
40
47
  end
41
48
 
42
49
  def to_param(&block)
43
- admin.to_param = block
50
+ admin.define_adapter_method(:to_param, &block)
44
51
  end
45
52
 
46
53
  def params(&block)
47
- admin.permitted_params = block
54
+ admin.define_adapter_method(:permitted_params, &block)
48
55
  end
49
56
 
50
57
  def decorator(decorator)
@@ -52,27 +59,28 @@ module Trestle
52
59
  end
53
60
 
54
61
  def decorate_collection(&block)
55
- admin.decorate_collection = block
62
+ admin.define_adapter_method(:decorate_collection, &block)
56
63
  end
57
64
 
58
65
  def merge_scopes(&block)
59
- admin.merge_scopes = block
66
+ admin.define_adapter_method(:merge_scopes, &block)
60
67
  end
61
68
 
62
69
  def sort(&block)
63
- admin.sort = block
70
+ admin.define_adapter_method(:sort, &block)
64
71
  end
65
72
 
66
73
  def sort_column(column, &block)
67
74
  admin.column_sorts[column.to_sym] = block
68
75
  end
69
76
 
70
- def paginate(&block)
71
- admin.paginate = block
77
+ def paginate(options={}, &block)
78
+ admin.pagination_options = admin.pagination_options.merge(options)
79
+ admin.define_adapter_method(:paginate, &block)
72
80
  end
73
81
 
74
82
  def count(&block)
75
- admin.count = block
83
+ admin.define_adapter_method(:count, &block)
76
84
  end
77
85
 
78
86
  def scope(name, scope=nil, options={}, &block)
@@ -83,6 +91,24 @@ module Trestle
83
91
 
84
92
  admin.scopes[name] = Scope.new(admin, name, options, &(scope || block))
85
93
  end
94
+
95
+ def return_to(options={}, &block)
96
+ actions = options.key?(:on) ? Array(options[:on]) : [:create, :update, :destroy]
97
+
98
+ actions.each do |action|
99
+ admin.return_locations[action.to_sym] = block
100
+ end
101
+ end
102
+
103
+ protected
104
+ def normalize_table_options(name, options)
105
+ if name.is_a?(Hash)
106
+ # Default index table
107
+ name, options = :index, name.reverse_merge(sortable: true)
108
+ end
109
+
110
+ [name, options.reverse_merge(admin: admin)]
111
+ end
86
112
  end
87
113
  end
88
114
  end
@@ -0,0 +1,68 @@
1
+ module Trestle
2
+ class Resource
3
+ class Collection
4
+ delegate :collection, :paginate, :finalize_collection, :decorate_collection,
5
+ :scopes, :merge_scopes, :column_sorts, :sort, to: :@admin
6
+
7
+ def initialize(admin, options={})
8
+ @admin, @options = admin, options
9
+ end
10
+
11
+ def prepare(params)
12
+ collection = collection(params)
13
+ collection = apply_scopes(collection, params) if scope?
14
+ collection = apply_sorting(collection, params) if sort?
15
+ collection = paginate(collection, params) if paginate?
16
+ collection = finalize_collection(collection) if finalize?
17
+ collection = decorate_collection(collection) if decorate?
18
+ collection
19
+ end
20
+
21
+ def scope?
22
+ @options[:scope] != false
23
+ end
24
+
25
+ def sort?
26
+ @options[:sort] != false
27
+ end
28
+
29
+ def paginate?
30
+ @options[:paginate] != false
31
+ end
32
+
33
+ def finalize?
34
+ @options[:finalize] != false
35
+ end
36
+
37
+ def decorate?
38
+ @options[:decorate] != false
39
+ end
40
+
41
+ private
42
+ def apply_scopes(collection, params)
43
+ unscoped = collection(params)
44
+
45
+ active_scopes(params).reduce(collection) do |collection, scope|
46
+ merge_scopes(collection, scope.apply(unscoped))
47
+ end
48
+ end
49
+
50
+ def active_scopes(params)
51
+ scopes.values.select { |s| s.active?(params) }
52
+ end
53
+
54
+ def apply_sorting(collection, params)
55
+ return collection unless params[:sort]
56
+
57
+ field = params[:sort].to_sym
58
+ order = params[:order].to_s.downcase == "desc" ? :desc : :asc
59
+
60
+ if column_sorts.has_key?(field)
61
+ @admin.instance_exec(collection, order, &column_sorts[field])
62
+ else
63
+ sort(collection, field, order)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end