admin_assistant 0.0.1 → 1.0.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 (49) hide show
  1. data/README +3 -28
  2. data/Rakefile +18 -10
  3. data/init.rb +1 -0
  4. data/install.rb +5 -1
  5. data/lib/admin_assistant/active_record_column.rb +211 -0
  6. data/lib/admin_assistant/association_target.rb +35 -0
  7. data/lib/admin_assistant/belongs_to_column.rb +186 -0
  8. data/lib/admin_assistant/builder.rb +222 -35
  9. data/lib/admin_assistant/column.rb +266 -297
  10. data/lib/admin_assistant/default_search_column.rb +41 -0
  11. data/lib/admin_assistant/file_column_column.rb +73 -0
  12. data/lib/admin_assistant/form_view.rb +7 -68
  13. data/lib/admin_assistant/helper.rb +4 -2
  14. data/lib/admin_assistant/index.rb +101 -81
  15. data/lib/admin_assistant/paperclip_column.rb +45 -0
  16. data/lib/admin_assistant/polymorphic_belongs_to_column.rb +102 -0
  17. data/lib/admin_assistant/request/autocomplete.rb +47 -0
  18. data/lib/admin_assistant/request/base.rb +176 -0
  19. data/lib/admin_assistant/request/create.rb +27 -0
  20. data/lib/admin_assistant/request/destroy.rb +15 -0
  21. data/lib/admin_assistant/request/edit.rb +11 -0
  22. data/lib/admin_assistant/request/index.rb +26 -0
  23. data/lib/admin_assistant/request/new.rb +19 -0
  24. data/lib/admin_assistant/request/show.rb +24 -0
  25. data/lib/admin_assistant/request/update.rb +44 -0
  26. data/lib/admin_assistant/search.rb +82 -0
  27. data/lib/admin_assistant/show_view.rb +20 -0
  28. data/lib/admin_assistant/virtual_column.rb +61 -0
  29. data/lib/admin_assistant.rb +190 -85
  30. data/lib/javascripts/admin_assistant.js +253 -0
  31. data/lib/stylesheets/activescaffold.css +219 -0
  32. data/lib/stylesheets/default.css +119 -0
  33. data/lib/views/_polymorphic_field_search.html.erb +89 -0
  34. data/lib/views/_restricted_autocompleter.html.erb +53 -0
  35. data/lib/views/autocomplete.html.erb +11 -0
  36. data/lib/views/form.html.erb +6 -3
  37. data/lib/views/index.html.erb +53 -46
  38. data/lib/views/show.html.erb +19 -0
  39. data/vendor/ar_query/MIT-LICENSE +20 -0
  40. data/vendor/ar_query/README +0 -0
  41. data/vendor/ar_query/init.rb +1 -0
  42. data/vendor/ar_query/install.rb +1 -0
  43. data/vendor/ar_query/lib/ar_query.rb +137 -0
  44. data/vendor/ar_query/spec/ar_query_spec.rb +253 -0
  45. data/vendor/ar_query/tasks/ar_query_tasks.rake +0 -0
  46. data/vendor/ar_query/uninstall.rb +1 -0
  47. metadata +39 -16
  48. data/lib/admin_assistant/request.rb +0 -183
  49. data/lib/stylesheets/admin_assistant.css +0 -75
@@ -0,0 +1,73 @@
1
+ class AdminAssistant
2
+ class FileColumnColumn < Column
3
+ attr_reader :name
4
+
5
+ def initialize(name)
6
+ @name = name.to_s
7
+ end
8
+
9
+ def contains?(column_name)
10
+ column_name.to_s == @name
11
+ end
12
+
13
+ class View < AdminAssistant::Column::View
14
+ def file_exists?(record)
15
+ if @file_exists_method
16
+ @file_exists_method.call record
17
+ else
18
+ !source_for_image_tag(record).nil?
19
+ end
20
+ end
21
+
22
+ def image_html(record)
23
+ @action_view.image_tag(
24
+ source_for_image_tag(record), :size => @image_size
25
+ )
26
+ end
27
+
28
+ def source_for_image_tag(record)
29
+ if @file_url_method
30
+ @file_url_method.call record
31
+ else
32
+ @action_view.instance_variable_set :@record, record
33
+ @action_view.url_for_file_column 'record', @column.name
34
+ end
35
+ end
36
+ end
37
+
38
+ class FormView < View
39
+ include AdminAssistant::Column::FormViewMethods
40
+
41
+ def default_html(form)
42
+ if file_exists?(form.object)
43
+ check_box_tag = @action_view.check_box_tag(
44
+ "#{form.object.class.name.underscore}[#{name}(destroy)]"
45
+ )
46
+ <<-HTML
47
+ <p>Current image:<br />#{image_html(form.object)}</p>
48
+ <p>Remove: #{check_box_tag}</p>
49
+ <p>Update: #{form.file_field(name)}</p>
50
+ HTML
51
+ else
52
+ "<p>Add: #{form.file_field(name)}</p>"
53
+ end
54
+ end
55
+ end
56
+
57
+ class IndexView < View
58
+ include AdminAssistant::Column::IndexViewMethods
59
+
60
+ def unconfigured_html(record)
61
+ image_html(record) if file_exists?(record)
62
+ end
63
+ end
64
+
65
+ class ShowView < View
66
+ include AdminAssistant::Column::ShowViewMethods
67
+
68
+ def html(record)
69
+ image_html(record) if file_exists?(record)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -13,52 +13,17 @@ class AdminAssistant
13
13
  end
14
14
  end
15
15
 
16
- def after_column_html(column)
17
- if after = render_from_custom_template("_after_#{column.name}_input")
18
- after
19
- else
20
- helper_method = "after_#{column.name}_input"
21
- if @action_view.respond_to?(helper_method)
22
- @action_view.send(helper_method, @record)
23
- end
24
- end
25
- end
26
-
27
- def column_html(column, rails_form)
28
- hff = render_from_custom_template "_#{column.name}_input"
29
- hff ||= column_html_from_helper_method(column)
30
- hff ||= if settings.read_only.include?(column.name)
31
- column.form_value(@record)
32
- elsif column.respond_to?(:add_to_form)
33
- column.add_to_form(rails_form)
34
- else
35
- virtual_column_html column
36
- end
37
- if ah = after_column_html(column)
38
- hff << ah
39
- end
40
- hff
41
- end
42
-
43
- def column_html_from_helper_method(column)
44
- html_method = "#{column.name}_html_for_form"
45
- if @action_view.respond_to?(html_method)
46
- @action_view.send(html_method, @record)
47
- end
48
- end
49
-
50
16
  def column_names
51
- settings.column_names ||
52
- model_class.columns.reject { |ar_column|
53
- %w(id created_at updated_at).include?(ar_column.name)
54
- }.map { |ar_column|
55
- @admin_assistant.column_name_or_assoc_name(ar_column.name)
56
- }
17
+ if %w(new create).include?(@action_view.action_name)
18
+ settings.columns_for_new
19
+ elsif %w(edit update).include?(@action_view.action_name)
20
+ settings.columns_for_edit
21
+ end
57
22
  end
58
23
 
59
24
  def columns
60
- @admin_assistant.columns(column_names).map { |c|
61
- c.view(@action_view, :input => settings.inputs[c.name.to_sym])
25
+ @admin_assistant.accumulate_columns(column_names).map { |c|
26
+ c.form_view @action_view, @admin_assistant
62
27
  }
63
28
  end
64
29
 
@@ -83,18 +48,6 @@ class AdminAssistant
83
48
  @admin_assistant.model_class
84
49
  end
85
50
 
86
- def render_from_custom_template(slug)
87
- template = File.join(
88
- RAILS_ROOT, 'app/views', controller.controller_path, "#{slug}.html.erb"
89
- )
90
- if File.exist?(template)
91
- @action_view.render(
92
- :file => template,
93
- :locals => {model_class.name.underscore.to_sym => @record}
94
- )
95
- end
96
- end
97
-
98
51
  def settings
99
52
  @admin_assistant.form_settings
100
53
  end
@@ -106,19 +59,5 @@ class AdminAssistant
106
59
  def title
107
60
  (@record.id ? "Edit" : "New") + " #{@admin_assistant.model_class_name}"
108
61
  end
109
-
110
- def virtual_column_html(column)
111
- input_name = "#{model_class.name.underscore}[#{column.name}]"
112
- input_type = settings.inputs[column.name.to_sym]
113
- fv = column.form_value @record
114
- if input_type
115
- if input_type == :check_box
116
- @action_view.send(:check_box_tag, input_name, '1', fv) +
117
- @action_view.send(:hidden_field_tag, input_name, '0')
118
- end
119
- else
120
- @action_view.send(:text_field_tag, input_name, fv)
121
- end
122
- end
123
62
  end
124
63
  end
@@ -1,7 +1,9 @@
1
1
  class AdminAssistant
2
2
  module Helper
3
- def admin_assistant_includes
4
- stylesheet_link_tag 'admin_assistant'
3
+ def admin_assistant_includes(opts = {})
4
+ theme = opts[:theme] || 'default'
5
+ stylesheet_link_tag("admin_assistant/#{theme}") +
6
+ javascript_include_tag('admin_assistant')
5
7
  end
6
8
  end
7
9
  end
@@ -2,32 +2,37 @@ require 'ar_query'
2
2
 
3
3
  class AdminAssistant
4
4
  class Index
5
- def initialize(admin_assistant, url_params = {})
6
- @admin_assistant = admin_assistant
7
- @url_params = url_params
5
+ attr_reader :controller_methods
6
+
7
+ def initialize(admin_assistant, url_params = {}, controller_methods = {})
8
+ @admin_assistant, @url_params, @controller_methods =
9
+ admin_assistant, url_params, controller_methods
8
10
  end
9
11
 
10
12
  def belongs_to_sort_column
11
- columns.detect { |column|
12
- column.is_a?(BelongsToColumn) && column.name.to_s == @url_params[:sort]
13
+ @admin_assistant.accumulate_belongs_to_columns(column_names).detect { |c|
14
+ c.name.to_s == sort
13
15
  }
14
16
  end
15
17
 
18
+ def column_names
19
+ settings.column_names || model_class.columns.map(&:name)
20
+ end
21
+
16
22
  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
23
+ @admin_assistant.accumulate_columns column_names
21
24
  end
22
25
 
23
- def conditions
26
+ def conditions_from_settings
24
27
  settings.conditions
25
28
  end
26
29
 
27
30
  def find_include
31
+ fi = settings.include || []
28
32
  if by_assoc = belongs_to_sort_column
29
- by_assoc.name
33
+ fi << by_assoc.name
30
34
  end
35
+ fi
31
36
  end
32
37
 
33
38
  def model_class
@@ -53,25 +58,34 @@ class AdminAssistant
53
58
  :order => order_sql, :include => find_include,
54
59
  :per_page => 25, :page => @url_params[:page]
55
60
  )
56
- search.add_to_query(ar_query)
57
- if conditions
58
- conditions_sql = conditions.call @url_params
61
+ if @controller_methods[:conditions_for_index]
62
+ sql = @controller_methods[:conditions_for_index].call
63
+ ar_query.condition_sqls << sql if sql
64
+ elsif conditions_from_settings
65
+ if conditions_from_settings.respond_to?(:call)
66
+ conditions_sql = conditions_from_settings.call @url_params
67
+ else
68
+ conditions_sql = conditions_from_settings
69
+ end
59
70
  ar_query.condition_sqls << conditions_sql if conditions_sql
60
71
  end
72
+ search.add_to_query(ar_query)
61
73
  if settings.total_entries
62
- ar_query[:total_entries] = settings.total_entries.call
74
+ ar_query.total_entries = settings.total_entries.call
63
75
  end
64
- @records = model_class.paginate :all, ar_query
76
+ @records = model_class.paginate :all, ar_query.to_hash
65
77
  end
66
78
  @records
67
79
  end
68
80
 
69
81
  def search
70
- @search ||= Search.new(@admin_assistant, @url_params['search'])
82
+ @search ||= AdminAssistant::Search.new(
83
+ @admin_assistant, @url_params['search']
84
+ )
71
85
  end
72
86
 
73
- def search_terms
74
- @url_params['search']
87
+ def search_requested?
88
+ !@url_params['search'].blank?
75
89
  end
76
90
 
77
91
  def settings
@@ -79,14 +93,17 @@ class AdminAssistant
79
93
  end
80
94
 
81
95
  def sort
82
- @url_params[:sort]
96
+ @url_params[:sort] ||
97
+ (settings.sort_by.to_s if settings.sort_by.is_a?(Symbol))
83
98
  end
84
99
 
85
100
  def sort_column
86
- if @url_params[:sort]
101
+ if sort
87
102
  columns.detect { |c|
88
- c.name.to_s == @url_params[:sort]
103
+ c.name.to_s == sort
89
104
  } || belongs_to_sort_column
105
+ elsif settings.sort_by.is_a?(Symbol)
106
+ columns.detect { |c| c.name == settings.sort_by.to_s }
90
107
  end
91
108
  end
92
109
 
@@ -95,86 +112,89 @@ class AdminAssistant
95
112
  end
96
113
 
97
114
  def view(action_view)
98
- View.new self, action_view
115
+ @view ||= View.new(self, action_view, @admin_assistant)
99
116
  end
100
117
 
101
- class Search
102
- def initialize(admin_assistant, search_params)
103
- @admin_assistant, @search_params = admin_assistant, search_params
104
- @search_params ||= {}
118
+ class View
119
+ def initialize(index, action_view, admin_assistant)
120
+ @index, @action_view, @admin_assistant =
121
+ index, action_view, admin_assistant
105
122
  end
106
123
 
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
124
+ def ajax_toggle_allowed?
125
+ @admin_assistant.update?
115
126
  end
116
127
 
117
128
  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
129
+ unless @columns
130
+ @columns = @index.columns.map { |c|
131
+ c.index_view @action_view, @admin_assistant, :index => @index
126
132
  }
127
- columns.each do |c|
128
- c.search_terms = @search_params[c.name]
129
- end
130
- columns
131
133
  end
134
+ @columns
132
135
  end
133
136
 
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)
137
+ def destroy?
138
+ @destroy ||= @admin_assistant.destroy?
146
139
  end
147
140
 
148
- def id
141
+ def edit?
142
+ @edit ||= @admin_assistant.edit?
149
143
  end
150
144
 
151
- def method_missing(meth, *args)
152
- if column = columns.detect { |c| c.name == meth.to_s }
153
- column.search_value
145
+ def header
146
+ if block = @index.settings.header
147
+ block.call @action_view.params
154
148
  else
155
- super
149
+ @admin_assistant.model_class_name.pluralize.capitalize
156
150
  end
157
151
  end
158
-
159
- def settings
160
- @admin_assistant.index_settings
152
+
153
+ def right_column?
154
+ edit? or destroy? or show? or !right_column_lambdas.empty?
161
155
  end
162
- end
163
-
164
- class View
165
- def initialize(index, action_view)
166
- @index, @action_view = index, action_view
156
+
157
+ def right_column_lambdas
158
+ @right_column_lambdas ||=
159
+ @admin_assistant.index_settings.right_column_links
167
160
  end
168
161
 
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
- }
162
+ def right_column_links(record)
163
+ links = ""
164
+ if edit?
165
+ links << @action_view.link_to(
166
+ 'Edit', :action => 'edit', :id => record.id
167
+ ) << " "
168
+ end
169
+ if destroy?
170
+ links << @action_view.link_to_remote(
171
+ 'Delete',
172
+ :url => {:action => 'destroy', :id => record.id},
173
+ :confirm => 'Are you sure?',
174
+ :success =>
175
+ "Effect.Fade('#{@admin_assistant.model_class.name.underscore}_#{record.id}')",
176
+ :method => :delete
177
+ ) << ' '
178
+ end
179
+ if show?
180
+ links << @action_view.link_to(
181
+ 'Show', :action => 'show', :id => record.id
182
+ ) << ' '
183
+ end
184
+ right_column_lambdas.each do |lambda|
185
+ link_args = lambda.call record
186
+ links << @action_view.link_to(*link_args)
187
+ end
188
+ if @action_view.respond_to?(:extra_right_column_links_for_index)
189
+ links << @action_view.extra_right_column_links_for_index(
190
+ record
191
+ ) || ''
192
+ end
193
+ links
194
+ end
195
+
196
+ def show?
197
+ @show ||= @admin_assistant.show?
178
198
  end
179
199
  end
180
200
  end
@@ -0,0 +1,45 @@
1
+ class AdminAssistant
2
+ class PaperclipColumn < Column
3
+ attr_reader :name
4
+
5
+ def initialize(name)
6
+ @name = name.to_s
7
+ end
8
+
9
+ def contains?(column_name)
10
+ column_name.to_s == @name ||
11
+ column_name.to_s =~
12
+ /^#{@name}_(file_name|content_type|file_size|updated_at)$/
13
+ end
14
+
15
+ class View < AdminAssistant::Column::View
16
+ def image_html(record)
17
+ @action_view.image_tag record.send(@column.name).url
18
+ end
19
+ end
20
+
21
+ class FormView < View
22
+ include AdminAssistant::Column::FormViewMethods
23
+
24
+ def default_html(form)
25
+ form.file_field name
26
+ end
27
+ end
28
+
29
+ class IndexView < View
30
+ include AdminAssistant::Column::IndexViewMethods
31
+
32
+ def html(record)
33
+ image_html record
34
+ end
35
+ end
36
+
37
+ class ShowView < View
38
+ include AdminAssistant::Column::ShowViewMethods
39
+
40
+ def html(record)
41
+ image_html record
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,102 @@
1
+ class AdminAssistant
2
+ class PolymorphicBelongsToColumn < Column
3
+ def initialize(belongs_to_assoc)
4
+ @belongs_to_assoc = belongs_to_assoc
5
+ end
6
+
7
+ def add_to_query_condition(ar_query_condition, search)
8
+ kv = (key_value = search.send(association_foreign_key)) &&
9
+ !key_value.blank?
10
+ tv = (type_value = search.send(foreign_type_field)) &&
11
+ !type_value.blank?
12
+ if kv and tv
13
+ ar_query_condition.add_condition do |subcond|
14
+ subcond.boolean_join = :and
15
+ subcond.sqls << "#{association_foreign_key} = ?"
16
+ subcond.bind_vars << key_value
17
+ subcond.sqls << "#{foreign_type_field} = ?"
18
+ subcond.bind_vars << type_value
19
+ end
20
+ end
21
+ end
22
+
23
+ def association_foreign_key
24
+ @belongs_to_assoc.association_foreign_key
25
+ end
26
+
27
+ def attributes_for_search_object(search_params)
28
+ atts = {}
29
+ atts[association_foreign_key.to_sym] =
30
+ search_params[association_foreign_key]
31
+ atts[foreign_type_field.to_sym] = search_params[foreign_type_field]
32
+ if !atts[foreign_type_field.to_sym].blank?
33
+ atts[name.to_sym] = Module.const_get(
34
+ search_params[foreign_type_field]
35
+ ).find_by_id(search_params[association_foreign_key])
36
+ else
37
+ atts[name.to_sym] = nil
38
+ end
39
+ atts
40
+ end
41
+
42
+ def contains?(column_name)
43
+ column_name.to_s == name
44
+ end
45
+
46
+ def foreign_type_field
47
+ @belongs_to_assoc.options[:foreign_type]
48
+ end
49
+
50
+ def match_text_fields_in_search
51
+ false
52
+ end
53
+
54
+ def name
55
+ @belongs_to_assoc.name.to_s
56
+ end
57
+
58
+ class FormView < AdminAssistant::Column::View
59
+ include AdminAssistant::Column::FormViewMethods
60
+
61
+ def default_html(form)
62
+ opts_for_select = @polymorphic_types.map { |t| [t.name, t.name] }
63
+ form.select(name + '_type', opts_for_select, @select_options) + " " +
64
+ form.text_field(name + '_id', :class => 'integer')
65
+ end
66
+ end
67
+
68
+ class IndexView < AdminAssistant::Column::View
69
+ include AdminAssistant::Column::IndexViewMethods
70
+
71
+ def value(record)
72
+ v = record.send name
73
+ if v
74
+ target = AssociationTarget.new v.class
75
+ str = target.name.capitalize
76
+ fv = target.assoc_value v
77
+ if fv
78
+ str << " '#{fv}'"
79
+ else
80
+ str << " #{v.id}"
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ class SearchView < AdminAssistant::Column::View
87
+ include AdminAssistant::Column::SearchViewMethods
88
+
89
+ def html(form)
90
+ @action_view.send(
91
+ :render,
92
+ :file => AdminAssistant.template_file('_polymorphic_field_search'),
93
+ :use_full_path => false,
94
+ :locals => {
95
+ :record => @search, :column => @column,
96
+ :polymorphic_types => @polymorphic_types
97
+ }
98
+ )
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,47 @@
1
+ class AdminAssistant
2
+ module Request
3
+ class Autocomplete < Base
4
+ def associated_class
5
+ @associated_class ||= Module.const_get(
6
+ underscored_assoc_class_name.camelize
7
+ )
8
+ end
9
+
10
+ def call
11
+ render_template_file(
12
+ 'autocomplete', :layout => false,
13
+ :locals => {
14
+ :records => records, :prefix => underscored_assoc_class_name,
15
+ :associated_class => associated_class
16
+ }
17
+ )
18
+ end
19
+
20
+ def records
21
+ action =~ /autocomplete_(.*)/
22
+ associated_class = Module.const_get $1.camelize
23
+ target = AssociationTarget.new associated_class
24
+ field = target.default_name_method
25
+ associated_class.find(
26
+ :all,
27
+ :conditions => [
28
+ "LOWER(#{field}) like ?",
29
+ "%#{search_string.downcase unless search_string.nil?}%"
30
+ ],
31
+ :limit => 10, :order => "length(#{field}), lower(#{field})"
32
+ )
33
+ end
34
+
35
+ def search_string
36
+ @controller.params[
37
+ "#{underscored_assoc_class_name}_autocomplete_input"
38
+ ]
39
+ end
40
+
41
+ def underscored_assoc_class_name
42
+ action =~ /autocomplete_(.*)/
43
+ $1
44
+ end
45
+ end
46
+ end
47
+ end