crud 0.0.3

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. checksums.yaml +7 -0
  2. data/CHANGELOG +1 -0
  3. data/LICENSE.md +674 -0
  4. data/README.md +136 -0
  5. data/Rakefile +72 -0
  6. data/app/assets/images/crud/search-icon.png +0 -0
  7. data/app/assets/javascripts/crud/application.js +17 -0
  8. data/app/assets/javascripts/crud/dashboard.js +7 -0
  9. data/app/assets/javascripts/crud/pagination.js +41 -0
  10. data/app/assets/stylesheets/crud/application.css +27 -0
  11. data/app/assets/stylesheets/crud/dashboard.css +4 -0
  12. data/app/assets/stylesheets/crud/flash_notices.css +19 -0
  13. data/app/assets/stylesheets/crud/pagination.css +104 -0
  14. data/app/assets/stylesheets/dataTables/jquery.dataTables-override.css +20 -0
  15. data/app/controllers/crud/crud_base_controller.rb +28 -0
  16. data/app/controllers/crud/crud_controller.rb +149 -0
  17. data/app/controllers/crud/dashboard_controller.rb +17 -0
  18. data/app/helpers/crud/crud_helper.rb +104 -0
  19. data/app/helpers/crud/dashboard_helper.rb +4 -0
  20. data/app/models/crud/klass_info.rb +166 -0
  21. data/app/models/crud/klass_list.rb +51 -0
  22. data/app/models/crud/menus/development_menu.rb +63 -0
  23. data/app/views/crud/crud/_attribute_value.html.erb +5 -0
  24. data/app/views/crud/crud/_error_messages.html.erb +10 -0
  25. data/app/views/crud/crud/_flash_messages.html.erb +10 -0
  26. data/app/views/crud/crud/_form.html.erb +22 -0
  27. data/app/views/crud/crud/_klass_data.html.erb +117 -0
  28. data/app/views/crud/crud/edit.html.erb +17 -0
  29. data/app/views/crud/crud/index.html.erb +6 -0
  30. data/app/views/crud/crud/index.js.erb +1 -0
  31. data/app/views/crud/crud/new.html.erb +13 -0
  32. data/app/views/crud/crud/show.html.erb +69 -0
  33. data/app/views/crud/dashboard/index.html.erb +15 -0
  34. data/app/views/layouts/crud/application.html.erb +13 -0
  35. data/config/cucumber.yml +8 -0
  36. data/config/initializers/inflections.rb +5 -0
  37. data/config/initializers/rails_engine.rb +8 -0
  38. data/config/initializers/will_paginate.rb +1 -0
  39. data/config/locales/de.yml +3 -0
  40. data/config/locales/en.yml +3 -0
  41. data/config/locales/es.yml +3 -0
  42. data/config/locales/fr.yml +3 -0
  43. data/config/routes.rb +16 -0
  44. data/crud.gemspec +29 -0
  45. data/lib/crud.rb +144 -0
  46. data/lib/crud/engine.rb +23 -0
  47. data/lib/crud/version.rb +3 -0
  48. data/lib/tasks/crud_tasks.rake +4 -0
  49. metadata +157 -0
@@ -0,0 +1,28 @@
1
+ module Crud
2
+ #class CrudBaseController < ActionController::Base
3
+ class CrudBaseController < ::Crud.parent_controller.constantize
4
+
5
+ helper ::Crud::CrudHelper
6
+
7
+ # Make these methods available to views
8
+ helper_method :is_allowed_to_view?
9
+ helper_method :is_allowed_to_update?
10
+
11
+ def is_allowed_to_view?
12
+ ::Crud.is_allowed_to_view
13
+ end
14
+
15
+ def is_allowed_to_update?
16
+ ::Crud.is_allowed_to_update
17
+ end
18
+
19
+ protected
20
+
21
+ def back_url
22
+ params[:back_url] || request.env['HTTP_REFERER'] || root_url
23
+ end
24
+
25
+ private
26
+
27
+ end
28
+ end
@@ -0,0 +1,149 @@
1
+ require_dependency "crud/crud_base_controller"
2
+
3
+ module Crud
4
+ class CrudController < CrudBaseController
5
+ around_filter :catch_not_found
6
+
7
+ before_filter :is_allowed_to_view?, :only => [:index, :show]
8
+ before_filter :is_allowed_to_update?, :only => [:new, :create, :edit, :update, :destroy, :delete]
9
+ before_filter :get_klass_info_from_params
10
+ before_filter :get_visible_attributes
11
+
12
+ # GET
13
+ def index
14
+ @search_prompt = I18n.t(:search_prompt)
15
+
16
+ page = (params[:page]) ? params[:page].to_i : 1
17
+ per_page = (params[:per_page]) ? params[:per_page].to_i : 10
18
+
19
+ if( params[:search] && !params[:search].empty? && params[:search] != @search_prompt)
20
+ where_clause = ''
21
+ @visible_attributes.each do |attribute|
22
+ where_clause += "LOWER(#{attribute[:column_name]}) LIKE '%#{params[:search].downcase}%' OR " if attribute[:column_data_type] == :string
23
+ end
24
+ where_clause.sub!(/ OR $/,'')
25
+ else
26
+ where_clause = nil
27
+ end
28
+
29
+ primary_key_name = @klass_info.primary_key(@klass_info[:class])
30
+ if primary_key_name
31
+ @klass_data = @klass_info[:class].where(where_clause).paginate(:page => page, :per_page => per_page).order("#{primary_key_name} ASC")
32
+ else
33
+ @klass_data = @klass_info[:class].where(where_clause).paginate(:page => page, :per_page => per_page)
34
+ end
35
+
36
+ respond_to do |format|
37
+ format.html # index.html.erb
38
+ format.js # index.js.erb
39
+ format.json { render :json => @klass_data }
40
+ format.xml { render :xml => @klass_data }
41
+ end
42
+ end
43
+
44
+ # GET
45
+ def show
46
+ @klass_data = @klass_info[:class].find(params[:id])
47
+
48
+ respond_to do |format|
49
+ format.html # index.html.erb
50
+ format.json { render :json => @klass_data }
51
+ format.xml { render :xml => @klass_data }
52
+ end
53
+ end
54
+
55
+ # GET
56
+ def new
57
+ @klass_data = @klass_info[:class].new
58
+
59
+ respond_to do |format|
60
+ format.html # index.html.erb
61
+ format.json { render :json => @klass_data }
62
+ format.xml { render :xml => @klass_data }
63
+ end
64
+ end
65
+
66
+ # GET
67
+ def edit
68
+ @klass_data = @klass_info[:class].find(params[:id])
69
+ end
70
+
71
+ # POST
72
+ def create
73
+ @klass_data = @klass_info[:class].new(params[@klass_info[:name_as_sym]])
74
+
75
+ respond_to do |format|
76
+ if @klass_data.save
77
+ format.html { redirect_to show_path(:class_name => @klass_info[:name], :id => @klass_data.id), :notice => "#{@klass_info[:name]} was successfully created" }
78
+ format.json { render :json => @klass_data, :status => :created, :location => @klass_data }
79
+ format.xml { head :ok }
80
+ else
81
+ format.html { render :action => 'new' }
82
+ format.json { render :json => @klass_data.errors, :status => :unprocessable_entity }
83
+ format.xml { render :xml => @klass_data.errors, :status => :unprocessable_entity }
84
+ end
85
+ end
86
+ end
87
+
88
+ # PUT
89
+ def update
90
+ @klass_data = @klass_info[:class].find(params[:id])
91
+
92
+ respond_to do |format|
93
+ if @klass_data.update_attributes(params[@klass_info[:name_as_sym]])
94
+ format.html { redirect_to show_path(:class_name => @klass_info[:name], :id => @klass_data.id), :notice => "#{@klass_info[:name]} was successfully updated" }
95
+ format.json { head :no_content }
96
+ format.xml { head :ok }
97
+ else
98
+ format.html { render :action => 'edit' }
99
+ format.json { render :json => @klass_data.errors, :status => :unprocessable_entity }
100
+ format.xml { render :xml => @klass_data.errors, :status => :unprocessable_entity }
101
+ end
102
+ end
103
+ end
104
+
105
+ # DELETE
106
+ def delete
107
+ @klass_data = @klass_info[:class].find(params[:id])
108
+ @klass_data.delete
109
+
110
+ respond_to do |format|
111
+ format.html { redirect_to index_path(:class_name => @klass_info[:name]), :notice => "#{@klass_info[:name]}[#{params[:id]}] was successfully deleted" }
112
+ format.json { head :no_content }
113
+ format.xml { head :ok }
114
+ end
115
+ end
116
+
117
+ # DELETE
118
+ def destroy
119
+ @klass_data = @klass_info[:class].find(params[:id])
120
+ @klass_data.destroy
121
+
122
+ respond_to do |format|
123
+ format.html { redirect_to index_path(:class_name => @klass_info[:name]), :notice => "#{@klass_info[:name]}[#{params[:id]}] was successfully destroyed" }
124
+ format.json { head :no_content }
125
+ format.xml { head :ok }
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def get_klass_info_from_params
132
+ if params[:class_name]
133
+ @klass_info = ::Crud::KlassInfo.new(params[:class_name].constantize)
134
+ else
135
+ render :text => "Missing class identifier"
136
+ end
137
+ end
138
+
139
+ def get_visible_attributes
140
+ @visible_attributes = @klass_info.visible_attributes(self.action_name.to_sym)
141
+ end
142
+
143
+ def catch_not_found
144
+ yield
145
+ rescue ActiveRecord::RecordNotFound
146
+ redirect_to back_url, :flash => { :error => "#{@klass_info[:name]} record with id=[#{params[:id]}] not found" }
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,17 @@
1
+ require_dependency "crud/crud_base_controller"
2
+
3
+ module Crud
4
+ class DashboardController < CrudBaseController
5
+ before_filter :is_allowed_to_view?, :only => [:index]
6
+ before_filter :get_klass_list
7
+
8
+ def index
9
+ end
10
+
11
+ private
12
+
13
+ def get_klass_list
14
+ @klass_list = Crud::KlassList.new() unless @klass_list
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,104 @@
1
+ module Crud
2
+ module CrudHelper
3
+
4
+ def options_for_select_tag
5
+ options_for_select(@klass_list.all.collect{|klass| [klass.to_s, index_path(:class_name => klass)]})
6
+ end
7
+
8
+ def edit_field_for_attribute(klass_info, row, attribute, f)
9
+ begin
10
+ html = []
11
+ if ['id','created_at', 'updated_at'].include?(attribute[:column_name])
12
+ html << '(auto)'
13
+ elsif attribute[:association] == :belongs_to
14
+ unless(attribute[:association_polymorphic])
15
+ html << f.collection_select(attribute[:column_name].to_sym, attribute[:association_class].all, :id, :to_s, :include_blank => true)
16
+ else
17
+ associated_attribute = klass_info.associated_polymorphic_attribute(attribute)
18
+ if associated_attribute && associated_attribute.length == 1 && row.attributes[associated_attribute[0][:column_name]]
19
+ html << f.collection_select(attribute[:column_name].to_sym, row.attributes[associated_attribute[0][:column_name]].constantize.all, :id, :to_s, :include_blank => true)
20
+ else
21
+ html << f.text_field(attribute[:column_name].to_sym)
22
+ end
23
+ end
24
+ else
25
+ case attribute[:column_data_type]
26
+ when :text
27
+ html << f.text_area(attribute[:column_name].to_sym, :rows => 6, :cols => 80)
28
+ when :datetime
29
+ html << f.datetime_select(attribute[:column_name].to_sym, :include_blank => true)
30
+ when :date
31
+ html << f.date_select(attribute[:column_name].to_sym, :discard_year => false, :include_blank => true)
32
+ when :time
33
+ html << f.time_select(attribute[:column_name].to_sym, :include_seconds => true, :include_blank => true)
34
+ when :boolean
35
+ html << f.select(attribute[:column_name].to_sym, [['True', true], ['False', false]], :include_blank => true)
36
+ else
37
+ html << f.text_field(attribute[:column_name].to_sym)
38
+ end
39
+ end
40
+ html.join
41
+ rescue Exception => e
42
+ Rails.logger.info "#{e.class}: #{e.message}#$/#{e.backtrace.join($/)}"
43
+ "problem constructing edit field for '#{attribute[:column_name].to_s}'"
44
+ end
45
+ end
46
+
47
+ def render_attribute_value(attribute, klass_data)
48
+ # Parameters :attribute => attribute, :klass_data => row_data
49
+ custom_fields = @klass_info.custom_fields(attribute[:column_name], controller.action_name.to_sym)
50
+ custom_fields_contain_a_replacement = @klass_info.custom_fields_contain_a_replacement(custom_fields)
51
+
52
+ unless custom_fields_contain_a_replacement
53
+ attribute_value_from_row = @klass_info.attribute_value_from_row(attribute, klass_data)
54
+ formatted_output = attribute_value_from_row[1] ? "[#{attribute_value_from_row[0]}] #{attribute_value_from_row[1]}" : "#{attribute_value_from_row[0]}"
55
+
56
+ unless (attribute[:association] && attribute[:association] == :belongs_to)
57
+ return formatted_output
58
+ else
59
+ unless attribute[:association_polymorphic]
60
+ return link_to formatted_output, show_path(:class_name => attribute[:association_class].name, :id => attribute_value_from_row[0])
61
+ else
62
+ associated_polymorphic_class = @klass_info.associated_polymorphic_class(attribute, klass_data)
63
+ if associated_polymorphic_class
64
+ return link_to formatted_output, show_path(:class_name => associated_polymorphic_class.name, :id => attribute_value_from_row[0])
65
+ else
66
+ return formatted_output
67
+ end
68
+ end
69
+ end
70
+ else
71
+ ''.html_safe
72
+ end
73
+
74
+ end
75
+
76
+ def render_custom_fields(attribute, klass_data, klass_info)
77
+ out = ''
78
+
79
+ custom_fields = klass_info.custom_fields(attribute[:column_name], controller.action_name.to_sym)
80
+ custom_fields.each do |field|
81
+ out += render field[:partial], {field[:record_data_parameter].to_sym => klass_data}.merge(field[:additional_partial_parameters])
82
+ out += '<br/>'
83
+ end
84
+
85
+ out.html_safe
86
+ end
87
+
88
+ def render_attribute_value_and_custom_fields(attribute, klass_data, klass_info)
89
+ (render_attribute_value(attribute, klass_data) + render_custom_fields(attribute, klass_data, klass_info)).html_safe
90
+ end
91
+
92
+ private
93
+
94
+ # Handler to send application URIs back to the application
95
+ def method_missing(method, *args, &block)
96
+ if (method.to_s.end_with?('_path') || method.to_s.end_with?('_url')) && main_app.respond_to?(method)
97
+ main_app.send(method, *args)
98
+ else
99
+ super
100
+ end
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,4 @@
1
+ module Crud
2
+ module DashboardHelper
3
+ end
4
+ end
@@ -0,0 +1,166 @@
1
+ # Model data
2
+ module Crud
3
+ class KlassInfo < Hash
4
+
5
+ def initialize(class_name)
6
+ self[:name] = class_name.to_s
7
+ namespace_parts = self[:name].match(/(.+)::(.+)/)
8
+ self[:namespace] = namespace_parts ? "::#{namespace_parts[1]}::" : ''
9
+ self[:name_as_sym] = self[:name].gsub(/::/,'_').underscore.to_sym
10
+ self[:class] = self[:name].constantize
11
+ self[:column_settings] = ::Crud.column_settings.select {|v| ["::#{self[:name]}", self[:name], 'all'].include?(v[:class].to_s)}
12
+ self[:custom_fields] = ::Crud.custom_fields.select {|v| ["::#{self[:name]}", self[:name]].include?(v[:class].to_s)}
13
+ self[:custom_fields].each {|field| field[:additional_partial_parameters] ||= {}}
14
+
15
+ if self[:class].respond_to?(:reflections)
16
+
17
+ self[:attributes] = []
18
+
19
+ self[:class].columns.each_index do |i|
20
+ self[:attributes][i] = {}
21
+ self[:attributes][i][:column_name] = self[:class].column_names[i]
22
+ self[:attributes][i][:column_class_type] = self[:class].column_names[i].class
23
+ self[:attributes][i][:column_data_type] = self[:class].columns[i].type
24
+
25
+ # Initialise belongs_to associations
26
+ reflection_value = belongs_to_reflection_values.select {
27
+ |k| k.name == self[:attributes][i][:column_name].sub(/_id$/,'').to_sym
28
+ }[0]
29
+
30
+ if reflection_value
31
+ self[:attributes][i][:association] = reflection_value.macro
32
+ self[:attributes][i][:association_polymorphic] = reflection_value.options[:polymorphic] ? reflection_value.options[:polymorphic] : false
33
+ self[:attributes][i][:association_class] = association_class_for_reflection_value(reflection_value, :belongs_to, self[:attributes][i])
34
+ end
35
+ end
36
+
37
+ self[:has_one] = []
38
+ self[:has_many] = []
39
+ self[:has_and_belongs_to_many] = []
40
+
41
+ has_one_reflection_values.each_with_index do |reflection_value, i|
42
+ self[:has_one][i] = {}
43
+ self[:has_one][i][:association_name] = reflection_value.name
44
+ self[:has_one][i][:association_class] = association_class_for_reflection_value(reflection_value, :has_one)
45
+ end
46
+
47
+ has_many_reflection_values.each_with_index do |reflection_value, i|
48
+ self[:has_many][i] = {}
49
+ self[:has_many][i][:association_name] = reflection_value.name
50
+ self[:has_many][i][:association_class] = association_class_for_reflection_value(reflection_value, :has_many)
51
+ end
52
+
53
+ habtm_reflection_values.each_with_index do |reflection_value, i|
54
+ self[:has_and_belongs_to_many][i] = {}
55
+ self[:has_and_belongs_to_many][i][:association_name] = reflection_value.name
56
+ self[:has_and_belongs_to_many][i][:association_class] = association_class_for_reflection_value(reflection_value, :has_and_belongs_to_many)
57
+ end
58
+ end
59
+ end
60
+
61
+ def primary_key(klass)
62
+ klass.respond_to?(:primary_key) ? klass.primary_key : 'id'
63
+ end
64
+
65
+ def self.primary_key_is_id?(klass)
66
+ (klass.respond_to?(:primary_key) && klass.primary_key == 'id')
67
+ end
68
+
69
+ def row_id(row)
70
+ row.send( self.primary_key(row) )
71
+ end
72
+
73
+ def attribute_value_from_row(attribute, row)
74
+ if attribute[:association] == :belongs_to
75
+ association_id = row.attributes[attribute[:column_name]].to_i
76
+
77
+ unless(attribute[:association_polymorphic])
78
+ association_value = attribute[:association_class].find(association_id)
79
+ else
80
+ associated_attribute = associated_polymorphic_attribute(attribute)
81
+ if associated_attribute && associated_attribute.length == 1
82
+ association_value = row.attributes[associated_attribute[0][:column_name]].constantize.find(association_id)
83
+ end
84
+ end unless (association_id==0)
85
+ end
86
+
87
+ ["#{row.attributes[attribute[:column_name]]}", (association_value ? "#{association_value}" : nil)]
88
+ end
89
+
90
+ def associated_polymorphic_class(attribute, row = nil)
91
+ associated_class = nil
92
+ if (row)
93
+ associated_attribute = associated_polymorphic_attribute(attribute)
94
+ if associated_attribute && associated_attribute.length == 1 && row.attributes[associated_attribute[0][:column_name]]
95
+ associated_class = row.attributes[associated_attribute[0][:column_name]].constantize
96
+ end
97
+ end
98
+ associated_class
99
+ end
100
+
101
+ def associated_polymorphic_attribute(attribute)
102
+ self[:attributes].select do |attr|
103
+ attr[:column_name] == attribute[:column_name].sub(/_id$/,"_type")
104
+ end if attribute[:association_polymorphic]
105
+ end
106
+
107
+ def visible_attributes(page)
108
+ self[:attributes].select { |v| is_visible_attribute?(v[:column_name], page) }
109
+ end
110
+
111
+ def is_visible_attribute?(column, page)
112
+ ( self[:column_settings].select {|v| (v[:hidden_fields] ? v[:hidden_fields].include?(column.to_sym) : false) && (v[:only] ? v[:only].include?(page.to_sym) : true)} ).empty?
113
+ end
114
+
115
+ def custom_fields(column, page = nil)
116
+ self[:custom_fields].select {|v| (v[:column_name] ? v[:column_name] == column.to_s : false) && (v[:global] ? v[:global] == false : true) && ((page && v[:only]) ? v[:only].include?(page.to_sym) : true)}
117
+ end
118
+
119
+ def global_custom_fields(page = nil)
120
+ self[:custom_fields].select {|v| (v[:global] ? v[:global] == true : false) && ((page && v[:only]) ? v[:only].include?(page.to_sym) : true)}
121
+ end
122
+
123
+ def custom_fields_contain_a_replacement(fields)
124
+ replacement_fields = fields.select {|v| (v[:column_replacement] ? v[:column_replacement] == true : false)}
125
+ (replacement_fields.length > 0)
126
+ end
127
+
128
+ private
129
+
130
+ def belongs_to_reflection_values()
131
+ reflection_values_for_klass(:belongs_to)
132
+ end
133
+
134
+ def has_one_reflection_values()
135
+ reflection_values_for_klass(:has_one)
136
+ end
137
+
138
+ def has_many_reflection_values()
139
+ reflection_values_for_klass(:has_many)
140
+ end
141
+
142
+ def habtm_reflection_values()
143
+ reflection_values_for_klass(:has_and_belongs_to_many)
144
+ end
145
+
146
+ def reflection_values_for_klass(association_type)
147
+ self[:class].reflections.values.select do |value|
148
+ value.macro == association_type
149
+ end
150
+ end
151
+
152
+ def association_class_for_reflection_value(reflection_value, association_type, attribute = nil, row = nil)
153
+ association_class = reflection_value.options[:class_name] ?
154
+ reflection_value.options[:class_name].to_s.sub(/^class_name/,'') :
155
+ [:has_many, :has_and_belongs_to_many].include?(association_type) ?
156
+ "#{self[:namespace]}#{reflection_value.name.to_s.camelize.classify}" : "#{self[:namespace]}#{reflection_value.name.to_s.camelize}"
157
+
158
+ if (attribute && attribute[:association_polymorphic])
159
+ associated_polymorphic_class(attribute, row)
160
+ else
161
+ association_class.constantize
162
+ end
163
+ end
164
+
165
+ end
166
+ end