crud 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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