activeadmin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activeadmin might be problematic. Click here for more details.

Files changed (89) hide show
  1. data/.document +5 -0
  2. data/.gitignore +25 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +201 -0
  6. data/Rakefile +71 -0
  7. data/active_admin.gemspec +22 -0
  8. data/lib/active_admin.rb +229 -0
  9. data/lib/active_admin/action_builder.rb +60 -0
  10. data/lib/active_admin/action_items.rb +48 -0
  11. data/lib/active_admin/asset_registration.rb +34 -0
  12. data/lib/active_admin/breadcrumbs.rb +26 -0
  13. data/lib/active_admin/dashboards.rb +50 -0
  14. data/lib/active_admin/dashboards/dashboard_controller.rb +40 -0
  15. data/lib/active_admin/dashboards/renderer.rb +45 -0
  16. data/lib/active_admin/dashboards/section.rb +43 -0
  17. data/lib/active_admin/dashboards/section_renderer.rb +28 -0
  18. data/lib/active_admin/filters.rb +189 -0
  19. data/lib/active_admin/form_builder.rb +91 -0
  20. data/lib/active_admin/helpers/optional_display.rb +34 -0
  21. data/lib/active_admin/menu.rb +42 -0
  22. data/lib/active_admin/menu_item.rb +67 -0
  23. data/lib/active_admin/namespace.rb +111 -0
  24. data/lib/active_admin/page_config.rb +15 -0
  25. data/lib/active_admin/pages.rb +11 -0
  26. data/lib/active_admin/pages/base.rb +92 -0
  27. data/lib/active_admin/pages/edit.rb +21 -0
  28. data/lib/active_admin/pages/index.rb +58 -0
  29. data/lib/active_admin/pages/index/blog.rb +65 -0
  30. data/lib/active_admin/pages/index/table.rb +48 -0
  31. data/lib/active_admin/pages/index/thumbnails.rb +40 -0
  32. data/lib/active_admin/pages/new.rb +21 -0
  33. data/lib/active_admin/pages/show.rb +54 -0
  34. data/lib/active_admin/renderer.rb +72 -0
  35. data/lib/active_admin/resource.rb +96 -0
  36. data/lib/active_admin/resource_controller.rb +325 -0
  37. data/lib/active_admin/sidebar.rb +78 -0
  38. data/lib/active_admin/table_builder.rb +162 -0
  39. data/lib/active_admin/tabs_renderer.rb +39 -0
  40. data/lib/active_admin/version.rb +3 -0
  41. data/lib/active_admin/view_helpers.rb +106 -0
  42. data/lib/active_admin/views/active_admin_dashboard/index.html.erb +1 -0
  43. data/lib/active_admin/views/active_admin_default/edit.html.erb +1 -0
  44. data/lib/active_admin/views/active_admin_default/index.csv.erb +2 -0
  45. data/lib/active_admin/views/active_admin_default/index.html.erb +1 -0
  46. data/lib/active_admin/views/active_admin_default/new.html.erb +1 -0
  47. data/lib/active_admin/views/active_admin_default/show.html.erb +1 -0
  48. data/lib/active_admin/views/layouts/active_admin.html.erb +40 -0
  49. data/lib/activeadmin.rb +1 -0
  50. data/lib/generators/active_admin/install/install_generator.rb +31 -0
  51. data/lib/generators/active_admin/install/templates/active_admin.css +325 -0
  52. data/lib/generators/active_admin/install/templates/active_admin.js +10 -0
  53. data/lib/generators/active_admin/install/templates/active_admin.rb +47 -0
  54. data/lib/generators/active_admin/install/templates/active_admin_vendor.js +382 -0
  55. data/lib/generators/active_admin/install/templates/dashboards.rb +36 -0
  56. data/lib/generators/active_admin/install/templates/images/orderable.gif +0 -0
  57. data/lib/generators/active_admin/resource/resource_generator.rb +16 -0
  58. data/lib/generators/active_admin/resource/templates/admin.rb +3 -0
  59. data/spec/integration/dashboard_spec.rb +44 -0
  60. data/spec/integration/index_as_blog_spec.rb +65 -0
  61. data/spec/integration/index_as_csv_spec.rb +40 -0
  62. data/spec/integration/index_as_table_spec.rb +160 -0
  63. data/spec/integration/index_as_thumbnails_spec.rb +43 -0
  64. data/spec/integration/layout_spec.rb +82 -0
  65. data/spec/integration/new_view_spec.rb +52 -0
  66. data/spec/integration/show_view_spec.rb +91 -0
  67. data/spec/spec_helper.rb +104 -0
  68. data/spec/support/rails_template.rb +19 -0
  69. data/spec/unit/action_builder_spec.rb +76 -0
  70. data/spec/unit/action_items_spec.rb +41 -0
  71. data/spec/unit/active_admin_spec.rb +52 -0
  72. data/spec/unit/asset_registration_spec.rb +37 -0
  73. data/spec/unit/controller_filters_spec.rb +26 -0
  74. data/spec/unit/dashboard_section_spec.rb +63 -0
  75. data/spec/unit/dashboards_spec.rb +59 -0
  76. data/spec/unit/filter_form_builder_spec.rb +157 -0
  77. data/spec/unit/form_builder_spec.rb +238 -0
  78. data/spec/unit/menu_item_spec.rb +137 -0
  79. data/spec/unit/menu_spec.rb +53 -0
  80. data/spec/unit/namespace_spec.rb +107 -0
  81. data/spec/unit/registration_spec.rb +46 -0
  82. data/spec/unit/renderer_spec.rb +100 -0
  83. data/spec/unit/resource_controller_spec.rb +48 -0
  84. data/spec/unit/resource_spec.rb +197 -0
  85. data/spec/unit/routing_spec.rb +12 -0
  86. data/spec/unit/sidebar_spec.rb +96 -0
  87. data/spec/unit/table_builder_spec.rb +162 -0
  88. data/spec/unit/tabs_renderer_spec.rb +34 -0
  89. metadata +247 -0
@@ -0,0 +1,60 @@
1
+ module ActiveAdmin
2
+
3
+ # Member Actions give you the functionality of defining both the
4
+ # action and the route directly from your ActiveAdmin registration
5
+ # block.
6
+ #
7
+ # For example:
8
+ #
9
+ # ActiveAdmin.register Post do
10
+ # member_action :comments do
11
+ # @post = Post.find(params[:id]
12
+ # @comments = @post.comments
13
+ # end
14
+ # end
15
+ #
16
+ # Will create a new controller action comments and will hook it up to
17
+ # the named route (comments_admin_post_path) /admin/posts/:id/comments
18
+ #
19
+ # You can treat everything within the block as a standard Rails controller
20
+ # action.
21
+ #
22
+ module ActionBuilder
23
+
24
+ def self.included(base)
25
+ base.extend ClassMethods
26
+ end
27
+
28
+ module ClassMethods
29
+ def member_action(name, options = {}, &block)
30
+ active_admin_config.member_actions << ControllerAction.new(name, options)
31
+ define_method(name, &block || Proc.new{})
32
+ end
33
+
34
+ def clear_member_actions!
35
+ active_admin_config.clear_member_actions!
36
+ end
37
+
38
+
39
+ def collection_action(name, options = {}, &block)
40
+ active_admin_config.collection_actions << ControllerAction.new(name, options)
41
+ define_method(name, &block || Proc.new{})
42
+ end
43
+
44
+ def clear_collection_actions!
45
+ active_admin_config.clear_collection_actions!
46
+ end
47
+ end
48
+
49
+ class ControllerAction
50
+ attr_reader :name
51
+ def initialize(name, options = {})
52
+ @name, @options = name, options
53
+ end
54
+
55
+ def http_verb
56
+ @options[:method] ||= :get
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,48 @@
1
+ require 'active_admin/helpers/optional_display'
2
+
3
+ module ActiveAdmin
4
+ module ActionItems
5
+
6
+ def self.included(base)
7
+ base.send :extend, ClassMethods
8
+ base.class_inheritable_accessor :action_items
9
+ base.action_items = []
10
+ end
11
+
12
+ module ClassMethods
13
+ def action_item(options = {}, &block)
14
+ self.action_items << ActiveAdmin::ActionItems::ActionItem.new(options, &block)
15
+ end
16
+
17
+ def clear_action_items!
18
+ self.action_items = []
19
+ end
20
+
21
+ def action_items_for(action)
22
+ action_items.select{|item| item.display_on?(action) }
23
+ end
24
+ end
25
+
26
+ class ActionItem
27
+ include ActiveAdmin::OptionalDisplay
28
+
29
+ attr_accessor :block
30
+
31
+ def initialize(options = {}, &block)
32
+ @options, @block = options, block
33
+ normalize_display_options!
34
+ end
35
+ end
36
+
37
+ class Renderer < ActiveAdmin::Renderer
38
+ def to_html(action_items)
39
+ content_tag :div, :class => 'action_items' do
40
+ action_items.collect do |action_item|
41
+ instance_eval(&action_item.block)
42
+ end.join(" ")
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,34 @@
1
+ module ActiveAdmin
2
+ module AssetRegistration
3
+
4
+ # Stylesheets
5
+
6
+ def register_stylesheet(name)
7
+ stylesheets << name
8
+ end
9
+
10
+ def stylesheets
11
+ @stylesheets ||= []
12
+ end
13
+
14
+ def clear_stylesheets!
15
+ @stylesheets = []
16
+ end
17
+
18
+
19
+ # Javascripts
20
+
21
+ def register_javascript(name)
22
+ javascripts << name
23
+ end
24
+
25
+ def javascripts
26
+ @javascripts ||= []
27
+ end
28
+
29
+ def clear_javascripts!
30
+ @javascripts = []
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ module ActiveAdmin
2
+ module Breadcrumbs
3
+
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ protected
9
+
10
+ def add_breadcrumb(name, url = '')
11
+ @breadcrumbs ||= []
12
+ url = send(url) if url.is_a?(Symbol)
13
+ @breadcrumbs << [name, url]
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ def add_breadcrumb(name, url, options = {})
19
+ before_filter options do |controller|
20
+ controller.send(:add_breadcrumb, name, url)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ module ActiveAdmin
2
+ module Dashboards
3
+
4
+ autoload :DashboardController, 'active_admin/dashboards/dashboard_controller'
5
+ autoload :Renderer, 'active_admin/dashboards/renderer'
6
+ autoload :Section, 'active_admin/dashboards/section'
7
+ autoload :SectionRenderer, 'active_admin/dashboards/section_renderer'
8
+
9
+ @@sections = {}
10
+ mattr_accessor :sections
11
+
12
+ class << self
13
+
14
+ # Eval an entire block in the context of this module to build
15
+ # dashboards quicker.
16
+ #
17
+ # Example:
18
+ #
19
+ # ActiveAdmin::Dashboards.build do
20
+ # section "Recent Post" do
21
+ # # return a list of posts
22
+ # end
23
+ # end
24
+ #
25
+ def build(&block)
26
+ module_eval(&block)
27
+ end
28
+
29
+ # Add a new dashboard section to a namespace. If no namespace is given
30
+ # it will be added to the default namespace.
31
+ def add_section(name, options = {}, &block)
32
+ namespace = options.delete(:namespace) || ActiveAdmin.default_namespace
33
+ self.sections[namespace] ||= []
34
+ self.sections[namespace] << Section.new(namespace, name, options, &block)
35
+ self.sections[namespace].sort!
36
+ end
37
+ alias_method :section, :add_section
38
+
39
+ def sections_for_namespace(namespace)
40
+ @@sections[namespace] || []
41
+ end
42
+
43
+ def clear_all_sections!
44
+ @@sections = {}
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ module ActiveAdmin
2
+ module Dashboards
3
+ class DashboardController < ResourceController
4
+
5
+ # Render from here if not overriden
6
+ self.default_views = 'active_admin_dashboard'
7
+
8
+ clear_action_items!
9
+
10
+ def index
11
+ skip_sidebar!
12
+ @dashboard_sections = find_sections
13
+ render_or_default 'index'
14
+ end
15
+
16
+ protected
17
+
18
+ def set_current_tab
19
+ @current_tab = "Dashboard"
20
+ end
21
+
22
+ def find_sections
23
+ ActiveAdmin::Dashboards.sections_for_namespace(namespace)
24
+ end
25
+
26
+ def namespace
27
+ self.class.name.split('::').first.underscore.to_sym
28
+ end
29
+
30
+ # Return the current menu for the view. This is a helper method
31
+ def current_menu
32
+ ActiveAdmin.namespaces[namespace].menu
33
+ end
34
+
35
+ # Override to do nothing
36
+ def add_section_breadcrumb
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ module ActiveAdmin
2
+ module Dashboards
3
+ class Renderer < ::ActiveAdmin::Pages::Base
4
+
5
+ def main_content
6
+ if @dashboard_sections && @dashboard_sections.any?
7
+ render_sections(@dashboard_sections)
8
+ else
9
+ default_welcome_section
10
+ end
11
+ end
12
+
13
+ protected
14
+
15
+ def title
16
+ "Dashboard"
17
+ end
18
+
19
+ def render_sections(sections)
20
+ content_tag :table, :class => "dashboard" do
21
+ sections.in_groups_of(3, false).collect do |row|
22
+ content_tag :tr do
23
+ row.collect do |section|
24
+ content_tag :td, render_section(section)
25
+ end.join
26
+ end
27
+ end.join
28
+ end
29
+ end
30
+
31
+ # Renders each section using their renderer
32
+ def render_section(section)
33
+ renderer = section.renderer.new(self)
34
+ renderer.to_html(section)
35
+ end
36
+
37
+ def default_welcome_section
38
+ content_tag :p, :id => "dashboard_default_message" do
39
+ "Welcome to Active Admin. This is the default dashboard page. To add dashboard sections, checkout 'app/admin/dashboards.rb'"
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveAdmin
2
+ module Dashboards
3
+ class Section
4
+
5
+ @@renderers = {
6
+ :default => SectionRenderer
7
+ }
8
+ cattr_accessor :renderers
9
+
10
+ DEFAULT_PRIORITY = 10
11
+
12
+ attr_accessor :name, :block
13
+ attr_reader :namespace
14
+
15
+ def initialize(namespace, name, options = {}, &block)
16
+ @namespace = namespace
17
+ @name = name
18
+ @options = options
19
+ @block = block
20
+ end
21
+
22
+ def priority
23
+ @options[:priority] || DEFAULT_PRIORITY
24
+ end
25
+
26
+ # Sort by priority then by name
27
+ def <=>(other)
28
+ result = priority <=> other.priority
29
+ result = name.to_s <=> other.name.to_s if result == 0
30
+ result
31
+ end
32
+
33
+ # Returns the class to use as this sections renderer. Raises
34
+ # an exception if the renderer could not be found
35
+ def renderer
36
+ klass = Section.renderers[@options[:as] || :default]
37
+ raise StandardError, "Could not find the #{@options[:as].inspect} dashboard section renderer." unless klass
38
+ klass
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveAdmin
2
+ module Dashboards
3
+ class SectionRenderer < ::ActiveAdmin::Renderer
4
+
5
+ def to_html(section)
6
+ @section = section
7
+ content_tag :div, title_wrapped + content_wrapped, :class => 'dashboard_section', :id => "#{section.name.to_s.downcase.gsub(' ', '_')}_dashboard_section"
8
+ end
9
+
10
+ def title_wrapped
11
+ content_tag :h3, title
12
+ end
13
+
14
+ def title
15
+ @section.name.to_s.titleize
16
+ end
17
+
18
+ def content_wrapped
19
+ content_tag :div, content, :class => 'dashboard_section_content'
20
+ end
21
+
22
+ def content
23
+ instance_eval(&@section.block)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,189 @@
1
+ module ActiveAdmin
2
+ module Filters
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.send :helper_method, :filters_config
7
+ end
8
+
9
+ module ClassMethods
10
+ def filter(attribute, options = {})
11
+ return false if attribute.nil?
12
+ @filters ||= []
13
+ @filters << options.merge(:attribute => attribute)
14
+ end
15
+
16
+ def filters_config
17
+ @filters && @filters.any? ? @filters : default_filters_config
18
+ end
19
+
20
+ def reset_filters!
21
+ @filters = []
22
+ end
23
+
24
+ # Returns a sane set of filters by default for the object
25
+ def default_filters_config
26
+ default_association_filters + default_content_filters
27
+ end
28
+
29
+ # Returns a default set of filters for the associations
30
+ def default_association_filters
31
+ if resource_class.respond_to?(:reflections)
32
+ resource_class.reflections.collect{|name, r| { :attribute => name }}
33
+ else
34
+ []
35
+ end
36
+ end
37
+
38
+ # Returns a default set of filters for the content columns
39
+ def default_content_filters
40
+ if resource_class.respond_to?(:content_columns)
41
+ resource_class.content_columns.collect{|c| { :attribute => c.name.to_sym } }
42
+ else
43
+ []
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+ def filters_config
50
+ self.class.filters_config
51
+ end
52
+
53
+
54
+ class FormBuilder < FormBuilder
55
+
56
+ def filter(method, options = {})
57
+ return "" if method.nil? || method == ""
58
+ options[:as] ||= default_filter_type(method)
59
+ return "" unless options[:as]
60
+ content = skip_form_buffers do
61
+ send("filter_#{options.delete(:as)}_input", method, options)
62
+ end
63
+ @form_buffers.last << template.content_tag(:div, content, :class => "filter-form-field")
64
+ end
65
+
66
+ protected
67
+
68
+ def filter_string_input(method, options = {})
69
+ field_name = "#{method}_contains"
70
+
71
+ [ label(field_name, "Search #{method.to_s.titlecase}"),
72
+ text_field(field_name)
73
+ ].join("\n").html_safe
74
+ end
75
+
76
+ def filter_date_range_input(method, options = {})
77
+ gt_field_name = "#{method}_gte"
78
+ lt_field_name = "#{method}_lte"
79
+
80
+ [ label(gt_field_name, method.to_s.titlecase),
81
+ filter_date_text_field(gt_field_name),
82
+ " - ",
83
+ filter_date_text_field(lt_field_name)
84
+ ].join("\n").html_safe
85
+ end
86
+
87
+ def filter_date_text_field(method)
88
+ current_value = @object.send(method)
89
+ text_field(method, :size => 12, :class => "datepicker", :max => 10, :value => current_value.respond_to?(:strftime) ? current_value.strftime("%Y-%m-%d") : "")
90
+ end
91
+
92
+ def filter_numeric_input(method, options = {})
93
+ filters = numeric_filters_for_method(method, options.delete(:filters) || default_numeric_filters)
94
+ current_filter = current_numeric_scope(filters)
95
+ filter_select = @template.select_tag '', @template.options_for_select(filters, current_filter),
96
+ :onchange => "document.getElementById('#{method}_numeric').name = 'q[' + this.value + ']';"
97
+ filter_input = text_field current_filter, :size => 10, :id => "#{method}_numeric"
98
+
99
+ [ label(method),
100
+ filter_select,
101
+ " ",
102
+ filter_input
103
+ ].join("\n").html_safe
104
+ end
105
+
106
+ def numeric_filters_for_method(method, filters)
107
+ filters.collect{|scope| [scope[0], [method,scope[1]].join("_") ] }
108
+ end
109
+
110
+ # Returns the scope for which we are currently searching. If no search is available
111
+ # it returns the first scope
112
+ def current_numeric_scope(filters)
113
+ filters[1..-1].inject(filters.first){|a,b| object.send(b[1].to_sym) ? b : a }[1]
114
+ end
115
+
116
+ def default_numeric_filters
117
+ [['Equal To', 'eq'], ['Greater Than', 'gt'], ['Less Than', 'lt']]
118
+ end
119
+
120
+ def filter_select_input(method, options = {})
121
+ association_name = method.to_s.gsub(/_id$/, '').to_sym
122
+ input_name = (generate_association_input_name(method).to_s + "_eq").to_sym
123
+ collection = find_collection_for_column(association_name, options)
124
+
125
+ [ label(input_name, method.to_s.titlecase),
126
+ select(input_name, collection, options.merge(:include_blank => 'Any'))
127
+ ].join("\n").html_safe
128
+ end
129
+
130
+ def filter_check_boxes_input(method, options = {})
131
+ input_name = (generate_association_input_name(method).to_s + "_in").to_sym
132
+ collection = find_collection_for_column(method, options)
133
+ selected_values = @object.send(input_name) || []
134
+ checkboxes = collection.map do |c|
135
+ label = c.is_a?(Array) ? c.first : c
136
+ value = c.is_a?(Array) ? c.last : c
137
+ "<label><input type=\"checkbox\" name=\"q[#{input_name}][]\" value=\"#{value}\" #{selected_values.include?(value) ? "checked" : ""}/> #{label}</label>"
138
+ end.join("\n").html_safe
139
+
140
+ [ label(input_name, method.to_s.titlecase),
141
+ checkboxes
142
+ ].join("\n").html_safe
143
+ end
144
+
145
+ # Override the standard finder to accept a proc
146
+ def find_collection_for_column(method, options = {})
147
+ options = options.dup
148
+ case options[:collection]
149
+ when Proc
150
+ options[:collection] = options[:collection].call
151
+ end
152
+ super(method, options)
153
+ end
154
+
155
+ # Returns the default filter type for a given attribute
156
+ def default_filter_type(method)
157
+ if column = column_for(method)
158
+ case column.type
159
+ when :date, :datetime
160
+ return :date_range
161
+ when :string, :text
162
+ return :string
163
+ when :integer
164
+ return :select if method.to_s =~ /_id$/
165
+ return :numeric
166
+ when :float, :decimal
167
+ return :numeric
168
+ end
169
+ end
170
+
171
+ if reflection = reflection_for(method)
172
+ return :select if reflection.macro == :belongs_to
173
+ end
174
+ end
175
+
176
+ # Returns the column for an attribute on the object being searched
177
+ # if it exists. Otherwise returns nil
178
+ def column_for(method)
179
+ @object.base.columns_hash[method.to_s] if @object.base.respond_to?(:columns_hash)
180
+ end
181
+
182
+ # Returns the association reflection for the method if it exists
183
+ def reflection_for(method)
184
+ @object.base.reflect_on_association(method) if @object.base.respond_to?(:reflect_on_association)
185
+ end
186
+
187
+ end
188
+ end
189
+ end