admin_assistant 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ class AdminAssistant
2
+ module Helper
3
+ def admin_assistant_includes
4
+ stylesheet_link_tag 'admin_assistant'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,181 @@
1
+ require 'ar_query'
2
+
3
+ class AdminAssistant
4
+ class Index
5
+ def initialize(admin_assistant, url_params = {})
6
+ @admin_assistant = admin_assistant
7
+ @url_params = url_params
8
+ end
9
+
10
+ def belongs_to_sort_column
11
+ columns.detect { |column|
12
+ column.is_a?(BelongsToColumn) && column.name.to_s == @url_params[:sort]
13
+ }
14
+ end
15
+
16
+ def columns
17
+ column_names = settings.column_names || model_class.columns.map { |c|
18
+ @admin_assistant.column_name_or_assoc_name(c.name)
19
+ }
20
+ @admin_assistant.columns column_names
21
+ end
22
+
23
+ def conditions
24
+ settings.conditions
25
+ end
26
+
27
+ def find_include
28
+ if by_assoc = belongs_to_sort_column
29
+ by_assoc.name
30
+ end
31
+ end
32
+
33
+ def model_class
34
+ @admin_assistant.model_class
35
+ end
36
+
37
+ def order_sql
38
+ if (sc = sort_column)
39
+ first_part = if (by_assoc = belongs_to_sort_column)
40
+ by_assoc.order_sql_field
41
+ else
42
+ sc.name
43
+ end
44
+ "#{first_part} #{sort_order}"
45
+ else
46
+ settings.sort_by
47
+ end
48
+ end
49
+
50
+ def records
51
+ unless @records
52
+ ar_query = ARQuery.new(
53
+ :order => order_sql, :include => find_include,
54
+ :per_page => 25, :page => @url_params[:page]
55
+ )
56
+ search.add_to_query(ar_query)
57
+ if conditions
58
+ conditions_sql = conditions.call @url_params
59
+ ar_query.condition_sqls << conditions_sql if conditions_sql
60
+ end
61
+ if settings.total_entries
62
+ ar_query[:total_entries] = settings.total_entries.call
63
+ end
64
+ @records = model_class.paginate :all, ar_query
65
+ end
66
+ @records
67
+ end
68
+
69
+ def search
70
+ @search ||= Search.new(@admin_assistant, @url_params['search'])
71
+ end
72
+
73
+ def search_terms
74
+ @url_params['search']
75
+ end
76
+
77
+ def settings
78
+ @admin_assistant.index_settings
79
+ end
80
+
81
+ def sort
82
+ @url_params[:sort]
83
+ end
84
+
85
+ def sort_column
86
+ if @url_params[:sort]
87
+ columns.detect { |c|
88
+ c.name.to_s == @url_params[:sort]
89
+ } || belongs_to_sort_column
90
+ end
91
+ end
92
+
93
+ def sort_order
94
+ @url_params[:sort_order] || 'asc'
95
+ end
96
+
97
+ def view(action_view)
98
+ View.new self, action_view
99
+ end
100
+
101
+ class Search
102
+ def initialize(admin_assistant, search_params)
103
+ @admin_assistant, @search_params = admin_assistant, search_params
104
+ @search_params ||= {}
105
+ end
106
+
107
+ def [](name)
108
+ @search_params[name]
109
+ end
110
+
111
+ def add_to_query(ar_query)
112
+ columns.each do |column|
113
+ column.add_to_query ar_query
114
+ end
115
+ end
116
+
117
+ def columns
118
+ search_field_names = settings.search_fields
119
+ if search_field_names.empty?
120
+ [DefaultSearchColumn.new(
121
+ default_terms, @admin_assistant.model_class
122
+ )]
123
+ else
124
+ columns = search_field_names.map { |column_name|
125
+ @admin_assistant.column column_name.to_s
126
+ }
127
+ columns.each do |c|
128
+ c.search_terms = @search_params[c.name]
129
+ end
130
+ columns
131
+ end
132
+ end
133
+
134
+ def column_views(action_view)
135
+ columns.map { |c|
136
+ opts = {:search => self}
137
+ if c.respond_to?(:name)
138
+ opts[:boolean_labels] = settings.boolean_labels[c.name]
139
+ end
140
+ c.view(action_view, opts)
141
+ }
142
+ end
143
+
144
+ def default_terms
145
+ @search_params if @search_params.is_a?(String)
146
+ end
147
+
148
+ def id
149
+ end
150
+
151
+ def method_missing(meth, *args)
152
+ if column = columns.detect { |c| c.name == meth.to_s }
153
+ column.search_value
154
+ else
155
+ super
156
+ end
157
+ end
158
+
159
+ def settings
160
+ @admin_assistant.index_settings
161
+ end
162
+ end
163
+
164
+ class View
165
+ def initialize(index, action_view)
166
+ @index, @action_view = index, action_view
167
+ end
168
+
169
+ def columns
170
+ @index.columns.map { |c|
171
+ c.view(
172
+ @action_view,
173
+ :boolean_labels => @index.settings.boolean_labels[c.name],
174
+ :sort_order => (@index.sort_order if c.name == @index.sort),
175
+ :link_to_args => @index.settings.link_to_args[c.name.to_sym]
176
+ )
177
+ }
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,183 @@
1
+ class AdminAssistant
2
+ module Request
3
+ class Base
4
+ def initialize(admin_assistant, controller)
5
+ @admin_assistant, @controller = admin_assistant, controller
6
+ end
7
+
8
+ def action
9
+ @controller.action_name
10
+ end
11
+
12
+ def after_form_html_template
13
+ File.join(
14
+ RAILS_ROOT, 'app/views/', @controller.controller_path,
15
+ '_after_form.html.erb'
16
+ )
17
+ end
18
+
19
+ def after_form_html_template_exists?
20
+ File.exist? after_form_html_template
21
+ end
22
+
23
+ def model_class
24
+ @admin_assistant.model_class
25
+ end
26
+
27
+ def model_class_symbol
28
+ model_class.name.underscore.to_sym
29
+ end
30
+
31
+ def params_for_save
32
+ params = {}
33
+ split_params = {}
34
+ whole_params = {}
35
+ @controller.params[model_class_symbol].each do |k, v|
36
+ k =~ /\([0-9]+i\)$/ ? (split_params[k] = v) : (whole_params[k] = v)
37
+ end
38
+ bases = split_params.map{ |k, v| k.gsub(/\([0-9]+i\)$/, '') }.uniq
39
+ bases.each do |b|
40
+ h = {}
41
+ split_params.each{ |k, v| h[k] = split_params.delete(k) if k =~ /#{b}\([0-9]+i\)$/ }
42
+ from_form_method = "#{b}_from_form".to_sym
43
+ if @controller.respond_to?(from_form_method)
44
+ params[b] = @controller.send(from_form_method, h)
45
+ elsif @record.respond_to?("#{b}=")
46
+ params.merge! h
47
+ end
48
+ end
49
+ whole_params.each do |k, v|
50
+ from_form_method = "#{k}_from_form".to_sym
51
+ if @controller.respond_to?(from_form_method)
52
+ params[k] = @controller.send(from_form_method, v)
53
+ elsif @record.respond_to?("#{k}=")
54
+ params[k] = v
55
+ end
56
+ end
57
+ params
58
+ end
59
+
60
+ def redirect_after_save
61
+ url_params = if @controller.respond_to?(:destination_after_save)
62
+ @controller.send(
63
+ :destination_after_save, @record, @controller.params
64
+ )
65
+ end
66
+ url_params ||= {:action => 'index'}
67
+ @controller.send :redirect_to, url_params
68
+ end
69
+
70
+ def render_after_form
71
+ @controller.send(
72
+ :render_to_string,
73
+ :file => after_form_html_template, :layout => false
74
+ )
75
+ end
76
+
77
+ def render_form
78
+ html = @controller.send(
79
+ :render_to_string, :file => template_file('form'), :layout => true
80
+ )
81
+ html << render_after_form if after_form_html_template_exists?
82
+ @controller.send :render, :text => html
83
+ end
84
+
85
+ def render_template_file(template_name = action, options_plus = {})
86
+ options = {:file => template_file(template_name), :layout => true}
87
+ options = options.merge options_plus
88
+ @controller.send(:render, options)
89
+ end
90
+
91
+ def save
92
+ if @controller.respond_to?(:before_save)
93
+ @controller.send(:before_save, @record)
94
+ end
95
+ @record.save
96
+ end
97
+
98
+ def template_file(template_name = action)
99
+ "#{File.dirname(__FILE__)}/../views/#{template_name}.html.erb"
100
+ end
101
+ end
102
+
103
+ class Create < Base
104
+ def call
105
+ @record = model_class.new
106
+ @record.attributes = params_for_save
107
+ if save
108
+ redirect_after_save
109
+ else
110
+ @controller.instance_variable_set :@record, @record
111
+ render_form
112
+ end
113
+ end
114
+
115
+ def save
116
+ if @controller.respond_to?(:before_create)
117
+ @controller.send(:before_create, @record)
118
+ end
119
+ result = super
120
+ if @controller.respond_to?(:after_create)
121
+ @controller.send(:after_create, @record)
122
+ end
123
+ result
124
+ end
125
+ end
126
+
127
+ class Destroy < Base
128
+ def call
129
+ @record = model_class.find @controller.params[:id]
130
+ @record.destroy
131
+ @controller.send :render, :text => ''
132
+ end
133
+ end
134
+
135
+ class Edit < Base
136
+ def call
137
+ @record = model_class.find @controller.params[:id]
138
+ @controller.instance_variable_set :@record, @record
139
+ render_form
140
+ end
141
+ end
142
+
143
+ class Index < Base
144
+ def call
145
+ index = AdminAssistant::Index.new(@admin_assistant, @controller.params)
146
+ @controller.instance_variable_set :@index, index
147
+ render_template_file
148
+ end
149
+
150
+ def columns
151
+ @admin_assistant.index_settings.columns
152
+ end
153
+ end
154
+
155
+ class New < Base
156
+ def call
157
+ @record = model_class.new
158
+ @controller.instance_variable_set :@record, @record
159
+ render_form
160
+ end
161
+ end
162
+
163
+ class Update < Base
164
+ def call
165
+ @record = model_class.find @controller.params[:id]
166
+ @record.attributes = params_for_save
167
+ if save
168
+ redirect_after_save
169
+ else
170
+ @controller.instance_variable_set :@record, @record
171
+ render_form
172
+ end
173
+ end
174
+
175
+ def save
176
+ if @controller.respond_to?(:before_update)
177
+ @controller.send(:before_update, @record)
178
+ end
179
+ super
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,172 @@
1
+ require 'admin_assistant/builder'
2
+ require 'admin_assistant/column'
3
+ require 'admin_assistant/form_view'
4
+ require 'admin_assistant/helper'
5
+ require 'admin_assistant/index'
6
+ require 'admin_assistant/request'
7
+ require 'will_paginate'
8
+
9
+ class AdminAssistant
10
+ attr_reader :custom_column_labels, :form_settings, :index_settings,
11
+ :model_class
12
+ attr_accessor :actions
13
+
14
+ def initialize(controller_class, model_class)
15
+ @controller_class, @model_class = controller_class, model_class
16
+ @actions = [:index, :create, :update]
17
+ @form_settings = FormSettings.new self
18
+ @index_settings = IndexSettings.new self
19
+ @custom_column_labels = {}
20
+ end
21
+
22
+ def belongs_to_assoc(name)
23
+ model_class.reflect_on_all_associations.detect { |assoc|
24
+ assoc.macro == :belongs_to && assoc.name.to_s == name.to_s
25
+ }
26
+ end
27
+
28
+ def column(name)
29
+ column = if file_columns.include?(name)
30
+ FileColumnColumn.new name
31
+ elsif (ar_column = model_class.columns_hash[name.to_s])
32
+ ActiveRecordColumn.new(ar_column)
33
+ elsif belongs_to_assoc = belongs_to_assoc(name)
34
+ BelongsToColumn.new(belongs_to_assoc)
35
+ else
36
+ AdminAssistantColumn.new(name)
37
+ end
38
+ if column && (custom = custom_column_labels[name.to_s])
39
+ column.custom_label = custom
40
+ end
41
+ column
42
+ end
43
+
44
+ def column_name_or_assoc_name(name)
45
+ result = name
46
+ ar_column = model_class.columns_hash[name.to_s]
47
+ if ar_column
48
+ associations = model_class.reflect_on_all_associations
49
+ if belongs_to_assoc = associations.detect { |assoc|
50
+ assoc.macro == :belongs_to && assoc.association_foreign_key == name
51
+ }
52
+ result = belongs_to_assoc.name.to_s
53
+ end
54
+ end
55
+ result
56
+ end
57
+
58
+ def columns(names)
59
+ columns = paperclip_attachments.map { |paperclip_attachment|
60
+ PaperclipColumn.new paperclip_attachment
61
+ }
62
+ names.each do |column_name|
63
+ unless columns.any? { |column| column.contains?(column_name) }
64
+ column = column column_name
65
+ columns << column if column
66
+ end
67
+ end
68
+ columns
69
+ end
70
+
71
+ def controller_actions
72
+ c_actions = actions.clone
73
+ c_actions << :new if c_actions.include?(:create)
74
+ c_actions << :edit if c_actions.include?(:update)
75
+ c_actions
76
+ end
77
+
78
+ def controller_css_class(controller)
79
+ controller.controller_path.gsub(%r|/|, '_')
80
+ end
81
+
82
+ def dispatch_to_request_method(request_method, controller)
83
+ controller.instance_variable_set :@admin_assistant, self
84
+ klass = Request.const_get request_method.to_s.capitalize
85
+ @request = klass.new(self, controller)
86
+ @request.call
87
+ @request = nil
88
+ end
89
+
90
+ def file_columns
91
+ fc = []
92
+ if model_class.respond_to?(:file_column)
93
+ model_class.columns.each do |column|
94
+ suffixes = %w( relative_path dir relative_dir temp )
95
+ if suffixes.all? { |suffix|
96
+ model_class.method_defined? "#{column.name}_#{suffix}".to_sym
97
+ }
98
+ fc << column.name
99
+ end
100
+ end
101
+ end
102
+ fc
103
+ end
104
+
105
+ def method_missing(meth, *args)
106
+ request_methods = [:create, :destroy, :edit, :index, :new, :update]
107
+ if request_methods.include?(meth) and args.size == 1
108
+ dispatch_to_request_method meth, args.first
109
+ else
110
+ if meth.to_s =~ /(.*)\?/ && request_methods.include?($1.to_sym)
111
+ @actions.include?($1.to_sym)
112
+ elsif @request.respond_to?(meth)
113
+ @request.send meth, *args
114
+ else
115
+ super
116
+ end
117
+ end
118
+ end
119
+
120
+ def model_class_name
121
+ model_class.name.gsub(/([A-Z])/, ' \1')[1..-1].downcase
122
+ end
123
+
124
+ def paperclip_attachments
125
+ pa = []
126
+ if model_class.respond_to?(:attachment_definitions)
127
+ if model_class.attachment_definitions
128
+ pa = model_class.attachment_definitions.map { |name, definition|
129
+ name
130
+ }
131
+ end
132
+ end
133
+ pa
134
+ end
135
+
136
+ def url_params(a = action)
137
+ {:controller => @controller_class.controller_path, :action => a}
138
+ end
139
+
140
+ module ControllerMethods
141
+ def self.included(controller)
142
+ controller.extend ControllerClassMethods
143
+ controller.class_inheritable_accessor :admin_assistant
144
+ end
145
+ end
146
+
147
+ module ControllerClassMethods
148
+ def admin_assistant_for(model_class, &block)
149
+ self.admin_assistant = AdminAssistant.new(self, model_class)
150
+ builder = Builder.new self.admin_assistant
151
+ if block
152
+ block.call builder
153
+ end
154
+ self.helper AdminAssistant::Helper
155
+ self.admin_assistant.controller_actions.each do |action|
156
+ self.send(:define_method, action) do
157
+ self.class.admin_assistant.send(action, self)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ ActionController::Base.send :include, AdminAssistant::ControllerMethods
165
+
166
+ FileUtils.copy(
167
+ "#{File.dirname(__FILE__)}/stylesheets/admin_assistant.css",
168
+ "#{RAILS_ROOT}/public/stylesheets/admin_assistant.css"
169
+ )
170
+ images_dir = "#{RAILS_ROOT}/public/images/admin_assistant"
171
+ FileUtils.mkdir(images_dir) unless File.exist?(images_dir)
172
+ FileUtils.cp_r(Dir.glob("#{File.dirname(__FILE__)}/images/*"), images_dir)
Binary file
Binary file
@@ -0,0 +1,75 @@
1
+ /******************************************************************************
2
+ Don't edit this file: It gets re-copied every time the server starts.
3
+ ******************************************************************************/
4
+
5
+ #admin_assistant { font-family: "Verdana","Arial", sans-serif }
6
+ #admin_assistant h2 { font-size: large; font-weight: bold }
7
+ #admin_assistant .actions {
8
+ position: absolute; right: 5px; top: 5px; text-align: right
9
+ }
10
+ #admin_assistant #header { position: relative }
11
+
12
+ /***** Index stuff ***********************************************************/
13
+
14
+ #admin_assistant.index table {
15
+ width: 100%; margin: 0px 0px 10px 0px; padding: 0px
16
+ }
17
+ #admin_assistant.index th {
18
+ background: #666; color: white; padding: 5px 20px 5px 5px
19
+ }
20
+ #admin_assistant.index th a {
21
+ color: white
22
+ }
23
+ #admin_assistant.index th.sort.asc {
24
+ background: #000 url(/images/admin_assistant/sort-asc.png) no-repeat right
25
+ }
26
+ #admin_assistant.index th.sort.desc {
27
+ background: #000 url(/images/admin_assistant/sort-desc.png) no-repeat right
28
+ }
29
+ #admin_assistant.index td {
30
+ background: white;
31
+ border-bottom: 1px solid #ccc;
32
+ border-right: 1px solid #ccc;
33
+ padding: 5px
34
+ }
35
+ #admin_assistant.index tbody tr td:first-child {
36
+ border-left: 1px solid #ccc;
37
+ }
38
+ #admin_assistant.index tbody tr.even td {
39
+ background: #eee;
40
+ }
41
+
42
+ #admin_assistant.index tbody tr td.sort {
43
+ background: #ddd
44
+ }
45
+ #admin_assistant.index tbody tr.even td.sort {
46
+ background: #ccc
47
+ }
48
+
49
+ /***** Form stuff ************************************************************/
50
+
51
+ #admin_assistant.form .column {
52
+ margin-bottom: 10px;
53
+ }
54
+
55
+ #admin_assistant.form label {
56
+ /* font-size: small;*/
57
+ }
58
+
59
+ #admin_assistant.form input {
60
+ font-size: large
61
+ }
62
+
63
+ #admin_assistant.form input[type=text] {
64
+ width: 100%
65
+ }
66
+
67
+ #admin_assistant.form select {
68
+ font-size: large
69
+ }
70
+
71
+ #admin_assistant.form textarea {
72
+ width: 100%;
73
+ font-size: large
74
+ }
75
+
@@ -0,0 +1,31 @@
1
+ <%
2
+ form_view = AdminAssistant::FormView.new(@record, @admin_assistant, self)
3
+ %>
4
+ <div id="admin_assistant" class="form <%=
5
+ @admin_assistant.controller_css_class(controller)
6
+ %>">
7
+ <% id = nil unless defined?(id) %>
8
+ <div id="header">
9
+ <h2><%= h(form_view.title) %></h2>
10
+ <div class="actions">
11
+ <%= link_to('Back to index', :action => 'index') %>
12
+ </div>
13
+ </div>
14
+ <% if !@record.errors.empty? %>
15
+ <%= error_messages_for 'record' %>
16
+ <% end %>
17
+ <% form_for(@record, form_view.form_for_args) do |rails_form| %>
18
+ <% form_view.columns.each do |column| %>
19
+ <div class="column">
20
+ <label><%= h(column.label) %></label>
21
+ <br /><%= form_view.column_html(column, rails_form) %>
22
+ </div>
23
+ <% end %>
24
+ <div>
25
+ <%= submit_tag(form_view.submit_value) %>
26
+ <% form_view.extra_submit_buttons.each do |extra_submit_button| %>
27
+ <%= submit_tag(extra_submit_button) %>
28
+ <% end %>
29
+ </div>
30
+ <% end %>
31
+ </div>