hammock 0.2.11.2

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 (47) hide show
  1. data/History.txt +67 -0
  2. data/LICENSE +24 -0
  3. data/Manifest.txt +46 -0
  4. data/README.rdoc +105 -0
  5. data/Rakefile +29 -0
  6. data/hammock.gemspec +43 -0
  7. data/lib/hammock/ajaxinate.rb +154 -0
  8. data/lib/hammock/callback.rb +16 -0
  9. data/lib/hammock/callbacks.rb +94 -0
  10. data/lib/hammock/canned_scopes.rb +125 -0
  11. data/lib/hammock/constants.rb +9 -0
  12. data/lib/hammock/controller_attributes.rb +52 -0
  13. data/lib/hammock/export_scope.rb +74 -0
  14. data/lib/hammock/hamlink_to.rb +47 -0
  15. data/lib/hammock/javascript_buffer.rb +63 -0
  16. data/lib/hammock/logging.rb +98 -0
  17. data/lib/hammock/model_attributes.rb +53 -0
  18. data/lib/hammock/model_logging.rb +30 -0
  19. data/lib/hammock/monkey_patches/action_pack.rb +32 -0
  20. data/lib/hammock/monkey_patches/active_record.rb +231 -0
  21. data/lib/hammock/monkey_patches/array.rb +73 -0
  22. data/lib/hammock/monkey_patches/hash.rb +57 -0
  23. data/lib/hammock/monkey_patches/logger.rb +28 -0
  24. data/lib/hammock/monkey_patches/module.rb +27 -0
  25. data/lib/hammock/monkey_patches/numeric.rb +25 -0
  26. data/lib/hammock/monkey_patches/object.rb +61 -0
  27. data/lib/hammock/monkey_patches/route_set.rb +28 -0
  28. data/lib/hammock/monkey_patches/string.rb +197 -0
  29. data/lib/hammock/overrides.rb +36 -0
  30. data/lib/hammock/resource_mapping_hooks.rb +28 -0
  31. data/lib/hammock/resource_retrieval.rb +119 -0
  32. data/lib/hammock/restful_actions.rb +172 -0
  33. data/lib/hammock/restful_rendering.rb +114 -0
  34. data/lib/hammock/restful_support.rb +186 -0
  35. data/lib/hammock/route_drawing_hooks.rb +22 -0
  36. data/lib/hammock/route_for.rb +59 -0
  37. data/lib/hammock/route_node.rb +159 -0
  38. data/lib/hammock/route_step.rb +87 -0
  39. data/lib/hammock/scope.rb +127 -0
  40. data/lib/hammock/suggest.rb +36 -0
  41. data/lib/hammock/utils.rb +42 -0
  42. data/lib/hammock.rb +29 -0
  43. data/misc/scaffold.txt +83 -0
  44. data/misc/template.rb +17 -0
  45. data/tasks/hammock_tasks.rake +5 -0
  46. data/test/hammock_test.rb +8 -0
  47. metadata +142 -0
@@ -0,0 +1,197 @@
1
+ module Hammock
2
+ module StringPatches
3
+ MixInto = String
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # Generates a random string consisting of +length+ hexadecimal characters (i.e. matching [0-9a-f]{length}).
13
+ def af09 length = 1
14
+ (1..length).inject('') {|a, t|
15
+ a << rand(16).to_s(16)
16
+ }
17
+ end
18
+
19
+ # Generates a random string consisting of +length+ alphamuneric characters (i.e. matching [0-9a-zA-Z]{length}).
20
+ def azAZ09 length = 1
21
+ (1..length).inject('') {|a, t|
22
+ a << ((r = rand(62)) < 36 ? r.to_s(36) : (r - 26).to_s(36).upcase)
23
+ }
24
+ end
25
+
26
+ end
27
+
28
+ module InstanceMethods
29
+
30
+ # Returns true iff +str+ appears exactly at the start of +self+.
31
+ def starts_with? str
32
+ self[0, str.length] == str
33
+ end
34
+
35
+ # Returns true iff +str+ appears exactly at the end of +self+.
36
+ def ends_with? str
37
+ self[-str.length, str.length] == str
38
+ end
39
+
40
+ # Return a duplicate of +self+, with +str+ prepended to it if it doesn't already start with +str+.
41
+ def start_with str
42
+ starts_with?(str) ? self : str + self
43
+ end
44
+
45
+ # Return a duplicate of +self+, with +str+ appended to it if it doesn't already end with +str+.
46
+ def end_with str
47
+ ends_with?(str) ? self : self + str
48
+ end
49
+
50
+ def possessive
51
+ "#{self}'#{'s' unless ends_with?('s')}"
52
+ end
53
+
54
+ # TODO any more to add?
55
+ NamePrefixes = %w[de den la von].freeze
56
+
57
+ def capitalize_name
58
+ split(' ').map {|term|
59
+ term.split('-').map {|term|
60
+ if NamePrefixes.include?(term)
61
+ term.downcase
62
+ elsif (term != term.downcase)
63
+ term
64
+ else # only capitalize words that are entirely lower case
65
+ term.capitalize
66
+ end
67
+ }.join('-')
68
+ }.join(' ')
69
+ end
70
+
71
+ def capitalize_name!
72
+ self.replace self.capitalize_name
73
+ end
74
+
75
+ # Returns whether this IP should be considered a valid one for a client to be using.
76
+ def valid_ip?
77
+ if production?
78
+ describe_as_ip == :public
79
+ else
80
+ describe_as_ip.in? :public, :private, :loopback
81
+ end
82
+ end
83
+
84
+ # Returns a symbol describing the class of IP address +self+ represents, if any.
85
+ #
86
+ # Examples:
87
+ #
88
+ # "Hello world!".valid_ip? #=> false
89
+ # "192.168.".valid_ip? #=> false
90
+ # "127.0.0.1".valid_ip? #=> :loopback
91
+ # "172.24.137.6".valid_ip? #=> :private
92
+ # "169.254.1.142".valid_ip? #=> :self_assigned
93
+ # "72.9.108.122".valid_ip? #=> :public
94
+ def describe_as_ip
95
+ parts = strip.split('.')
96
+ bytes = parts.zip(parts.map(&:to_i)).map {|(str,val)|
97
+ val if ((1..255) === val) || (val == 0 && str == '0')
98
+ }.squash
99
+
100
+ if bytes.length != 4
101
+ false
102
+ elsif bytes.starts_with? 0 # Source hosts on "this" network
103
+ :reserved
104
+ elsif bytes.starts_with? 127 # Loopback network; RFC1700
105
+ :loopback
106
+ elsif bytes.starts_with? 10 # Class-A private; RFC1918
107
+ :private
108
+ elsif bytes.starts_with?(172) && ((16..31) === bytes[1]) # Class-B private; RFC1918
109
+ :private
110
+ elsif bytes.starts_with? 169, 254 # Link-local range; RFC3330/3927
111
+ bytes[2].in?(0, 255) ? :reserved : :self_assigned
112
+ elsif bytes.starts_with? 192, 0, 2 # TEST-NET - used as example.com IP
113
+ :reserved
114
+ elsif bytes.starts_with? 192, 88, 99 # 6-to-4 relay anycast; RFC3068
115
+ :reserved
116
+ elsif bytes.starts_with? 192, 168 # Class-C private; RFC1918
117
+ :private
118
+ elsif bytes.starts_with? 198, 18 # Benchmarking; RFC2544
119
+ :reserved
120
+ else
121
+ :public
122
+ end
123
+ end
124
+
125
+ # Returns true if the string represents a valid email address.
126
+ def valid_email?
127
+ /^([a-z0-9\-\+\_\.]{2,})\@([a-z0-9\-]+\.)*([a-z0-9\-]{2,}\.)([a-z0-9\-]{2,})$/ =~ self
128
+ end
129
+
130
+ def colorize description = '', start_at = nil
131
+ if start_at.nil? || (cut_point = index(start_at)).nil?
132
+ Colorizer.colorize self, description
133
+ else
134
+ self[0...cut_point] + Colorizer.colorize(self[cut_point..-1], description)
135
+ end
136
+ end
137
+
138
+ def colorize! description = '', start_at = nil
139
+ replace colorize(description, start_at)
140
+ end
141
+
142
+ private
143
+
144
+ class Colorizer
145
+ HomeOffset = 29
146
+ LightOffset = 60
147
+ BGOffset = 10
148
+ LightRegex = /^light_/
149
+ ColorRegex = /^(light_)?none|gr[ae]y|red|green|yellow|blue|pink|cyan|white$/
150
+ CtrlRegex = /^bold|underlined?|blink(ing)?|reversed?$/
151
+ ColorOffsets = {
152
+ 'none' => 0,
153
+ 'gray' => 1, 'grey' => 1,
154
+ 'red' => 2,
155
+ 'green' => 3,
156
+ 'yellow' => 4,
157
+ 'blue' => 5,
158
+ 'pink' => 6,
159
+ 'cyan' => 7,
160
+ 'white' => 8
161
+ }
162
+ CtrlOffsets = {
163
+ 'bold' => 1,
164
+ 'underline' => 4, 'underlined' => 4,
165
+ 'blink' => 5, 'blinking' => 5,
166
+ 'reverse' => 7, 'reversed' => 7
167
+ }
168
+ class << self
169
+ def colorize text, description
170
+ terms = " #{description} ".gsub(' light ', ' light_').gsub(' on ', ' on_').strip.split(/\s+/)
171
+ bg = terms.detect {|i| /on_#{ColorRegex}/ =~ i }
172
+ fg = terms.detect {|i| ColorRegex =~ i }
173
+ ctrl = terms.detect {|i| CtrlRegex =~ i }
174
+
175
+ "\e[#{"0;#{fg_for(fg)};#{bg_for(bg) || ctrl_for(ctrl)}"}m#{text}\e[0m"
176
+ end
177
+
178
+ def fg_for name
179
+ light = name.gsub!(LightRegex, '') unless name.nil?
180
+ (ColorOffsets[name] || 0) + HomeOffset + (light ? LightOffset : 0)
181
+ end
182
+
183
+ def bg_for name
184
+ # There's a hole in the table on bg=none, so we use BGOffset to the left
185
+ offset = fg_for((name || '').sub(/^on_/, ''))
186
+ offset + BGOffset unless offset == HomeOffset
187
+ end
188
+
189
+ def ctrl_for name
190
+ CtrlOffsets[name] || HomeOffset
191
+ end
192
+ end
193
+ end
194
+
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,36 @@
1
+ module Hammock
2
+ module Overrides
3
+ def self.included base # :nodoc:
4
+ base.send :include, InstanceMethods
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ end
10
+
11
+ module InstanceMethods
12
+ private
13
+
14
+ def custom_scope
15
+ nil
16
+ end
17
+
18
+ def suggest_scope
19
+ mdl.readable_by current_user
20
+ end
21
+
22
+ def postsave_render result
23
+ nil
24
+ end
25
+
26
+ def postsave_redirect
27
+ nil
28
+ end
29
+
30
+ def postdestroy_redirect
31
+ nil
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ module Hammock
2
+ module ResourceMappingHooks
3
+ MixInto = ActionController::Resources
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, Methods
7
+
8
+ base.class_eval {
9
+ alias_method_chain_once :map_resource, :hammock_route_map
10
+ alias_method_chain_once :map_singleton_resource, :hammock_route_map
11
+ }
12
+ end
13
+
14
+ module Methods
15
+
16
+ def map_resource_with_hammock_route_map entity, options = {}, &block
17
+ ActionController::Routing::Routes.route_map.add entity, options
18
+ map_resource_without_hammock_route_map entity, options, &block
19
+ end
20
+
21
+ def map_singleton_resource_with_hammock_route_map entity, options = {}, &block
22
+ ActionController::Routing::Routes.route_map.add_singleton entity, options
23
+ map_singleton_resource_without_hammock_route_map entity, options, &block
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,119 @@
1
+ module Hammock
2
+ module ResourceRetrieval
3
+ def self.included base # :nodoc:
4
+ base.send :include, InstanceMethods
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ end
10
+
11
+ module InstanceMethods
12
+ private
13
+
14
+ def find_deleted_record
15
+ find_record :find_with_deleted
16
+ end
17
+
18
+ def find_record finder = :find
19
+ result = if !callback(:before_find)
20
+ # callbacks failed
21
+ elsif (record = retrieve_record(finder)).nil?
22
+ log "#{mdl}<#{params[:id]}> doesn't exist within #{requester_name.possessive} #{action_name} scope."
23
+ :not_found
24
+ elsif :ok != (verbability = can_verb_record?(action_name.to_sym, record))
25
+ verbability
26
+ elsif !callback(:during_find, record)
27
+ # callbacks failed
28
+ else
29
+ :ok
30
+ end
31
+
32
+ if :ok != result
33
+ escort(result)
34
+ else
35
+ assign_entity record
36
+ end
37
+ end
38
+
39
+ def retrieve_resource
40
+ if (scope = current_scope).nil?
41
+ escort :unauthed
42
+ else
43
+ if pagination_enabled?
44
+ assign_entity scope.paginate(:page => params[:page])
45
+ else
46
+ assign_entity scope
47
+ end
48
+ end
49
+ end
50
+
51
+ def retrieve_record finder
52
+ if (scope = current_scope).nil?
53
+
54
+ else
55
+ record = scope.send finder, :first, :conditions => {mdl.routing_attribute => params[:id]}
56
+ record || required_callback(:after_failed_find)
57
+ end
58
+ end
59
+
60
+ def escort reason
61
+ if rendered_or_redirected?
62
+ # lol
63
+ elsif request.xhr?
64
+ # TODO bad request might only be appropriate for invalid requests, as opposed to just an auth failure.
65
+ escort_for_bad_request
66
+ elsif :relogin == reason
67
+ reset_session # TODO: instead of a full reset, this should just set unauthed so we can remember who the user was without granting them their creds.
68
+ redirect_to returning_login_path
69
+ elsif :readonly == reason
70
+ escort_for_read_only
71
+ elsif :unauthed == reason
72
+ escort_for_403
73
+ elsif current_user.nil? && account_verb_scope?
74
+ escort_for_login
75
+ else
76
+ escort_for_404
77
+ end
78
+ false
79
+ end
80
+
81
+
82
+ private
83
+
84
+ def returning_login_path
85
+ session[:path_after_login] = request.request_uri
86
+ login_path
87
+ end
88
+
89
+ def escort_for_bad_request
90
+ log
91
+ render :nothing => true, :status => 400
92
+ end
93
+ def escort_for_read_only
94
+ log
95
+ redirect_to :action => :show
96
+ end
97
+ def escort_for_login
98
+ log
99
+ # render :partial => 'login/account', :status => 401 # unauthorized
100
+ redirect_to returning_login_path
101
+ end
102
+ def escort_for_404
103
+ if partial_exists? 'shared/status_404'
104
+ render :partial => 'shared/status_404', :layout => true
105
+ else
106
+ render :file => File.join(RAILS_ROOT, 'public/404.html'), :status => 404
107
+ end
108
+ end
109
+ def escort_for_403
110
+ if partial_exists? 'shared/status_404'
111
+ render :partial => 'shared/status_404', :layout => true
112
+ else
113
+ render :file => File.join(RAILS_ROOT, 'public/404.html'), :status => 403
114
+ end
115
+ end
116
+
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,172 @@
1
+ module Hammock
2
+ module RestfulActions
3
+ def self.included base # :nodoc:
4
+ base.send :include, InstanceMethods
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ end
10
+
11
+ module InstanceMethods
12
+
13
+ # The +index+ action. (GET, safe, idempotent)
14
+ #
15
+ # Lists the current resource's records that are visible within the current index scope, defined by +index_scope+ and +index_scope_for+ on the current model.
16
+ def index
17
+ if tasks_for_index
18
+ respond_to do |format|
19
+ format.html
20
+ format.xml { render :xml => @records.kick }
21
+ format.json { render :json => @records.kick }
22
+ format.yaml { render :text => @records.kick.to_yaml }
23
+ end
24
+ end
25
+ end
26
+
27
+ # The +new+ action. (GET, safe, idempotent)
28
+ #
29
+ # Renders a form containing the required fields to create a new record.
30
+ #
31
+ # This action is available within the same scope as +create+, since it is only useful if a subsequent +create+ would be successful.
32
+ def new
33
+ if !tasks_for_new
34
+ escort :unauthed
35
+ else
36
+ render_for_safe_actions
37
+ end
38
+ end
39
+
40
+ # The +create+ action. (POST, unsafe, non-idempotent)
41
+ #
42
+ # Creates a new record with the supplied attributes. TODO
43
+ def create
44
+ if !find_record_on_create && !assign_createable
45
+ escort :unauthed
46
+ else
47
+ render_or_redirect_after save_record
48
+ end
49
+ end
50
+
51
+ # The +show+ action. (GET, safe, idempotent)
52
+ #
53
+ # Displays the specified record if it is within the current read scope, defined by +read_scope+ and +read_scope_for+ on the current model.
54
+ def show
55
+ if find_record
56
+ render_for_safe_actions if callback(:before_show)
57
+ end
58
+ end
59
+
60
+ # The +edit+ action. (GET, safe, idempotent)
61
+ #
62
+ # Renders a form containing the fields populated with the current record's attributes.
63
+ #
64
+ # This action is available within the same scope as +update+, since it is only useful if a subsequent +update+ would be successful.
65
+ def edit
66
+ if find_record
67
+ render_for_safe_actions if callback(:before_modify) and callback(:before_edit)
68
+ end
69
+ end
70
+
71
+ # The +update+ action. (PUT, unsafe, idempotent)
72
+ #
73
+ # Updates the specified record with the supplied attributes if it is within the current write scope, defined by +write_scope+ and +write_scope_for+ on the current model.
74
+ def update
75
+ if find_record
76
+ # If params[:attribute] is given, update only that attribute. We mass-assign either way to filter through attr_accessible.
77
+ @record.attributes = if (attribute_name = params[:attribute])
78
+ { attribute_name => params_for(mdl.symbolize)[attribute_name] }
79
+ else
80
+ params_for mdl.symbolize
81
+ end
82
+
83
+ render_or_redirect_after save_record
84
+ end
85
+ end
86
+
87
+ # The +destroy+ action. (DELETE, unsafe, non-idempotent)
88
+ #
89
+ # Destroys the specified record if it is within the current write scope, defined by +write_scope+ and +write_scope_for+ on the current model.
90
+ def destroy
91
+ if find_record
92
+ result = callback(:before_destroy) and @record.destroy and callback(:after_destroy)
93
+ render_for_destroy result
94
+ end
95
+ end
96
+
97
+ # The +undestroy+ action. (POST, unsafe, idempotent)
98
+ #
99
+ # Reverses a previous destroy on the specified record if it is within the current write scope, defined by +write_scope+ and +write_scope_for+ on the current model.
100
+ def undestroy
101
+ if find_deleted_record
102
+ result = callback(:before_undestroy) and @record.undestroy and callback(:after_undestroy)
103
+ render_for_destroy result
104
+ end
105
+ end
106
+
107
+ # The +suggest+ action. (GET, safe, idempotent)
108
+ #
109
+ # Lists the current resource's records that would be listed by an index, filtered to show only those where at least one of the specified keys matches each whitespace-separated term in the query.
110
+ def suggest
111
+ @results = if params[:q].blank?
112
+ log 'No query specified.'
113
+ elsif params[:fields].blank?
114
+ log "No fields specified."
115
+ elsif !callback(:before_suggest)
116
+ # fail
117
+ else
118
+ fields = params[:fields].split(',')
119
+ @queries = params[:q].downcase.split(/\s+/)
120
+
121
+ mdl.send :with_scope, :find => suggest_scope.to_hash do
122
+ mdl.suggest fields, @queries
123
+ end
124
+ end
125
+
126
+ if @results.nil?
127
+ escort_for_bad_request
128
+ else
129
+ callback(:after_suggest)
130
+ render :action => "suggest_#{fields.join(',')}", :layout => false
131
+ end
132
+ end
133
+
134
+
135
+ private
136
+
137
+ def tasks_for_index
138
+ retrieve_resource and callback(:before_index) and (!inline_createable_resource? || tasks_for_new)
139
+ end
140
+
141
+ def tasks_for_new
142
+ callback(:before_modify) and callback(:before_new) if assign_createable
143
+ end
144
+
145
+ def find_record_on_create
146
+ if findable_on_create?
147
+ if record = current_nest_scope.find(:first, :conditions => params_for(mdl.symbolize))
148
+ log "suitable record already exists: #{record}"
149
+ assign_entity record
150
+ else
151
+ log "couldn't find a suitable record, proceeding with creation."
152
+ end
153
+ end
154
+ end
155
+
156
+ def save_record
157
+ verb = @record.new_record? ? 'create' : 'update'
158
+ if callback("before_#{verb}") and callback(:before_save) and save
159
+ callback("after_#{verb}") and callback(:after_save)
160
+ else
161
+ log "#{mdl} errors: " + @record.errors.full_messages.join(', ')
162
+ callback("after_failed_#{verb}") and callback(:after_failed_save) and false
163
+ end
164
+ end
165
+
166
+ def save
167
+ @record.save
168
+ end
169
+
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,114 @@
1
+ module Hammock
2
+ module RestfulRendering
3
+ def self.included base # :nodoc:
4
+ base.send :include, InstanceMethods
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ end
10
+
11
+ module InstanceMethods
12
+
13
+ private
14
+
15
+ def render_or_redirect_after result
16
+ if request.xhr?
17
+ render_for_safe_actions result, :editable => true, :edit => false
18
+ else
19
+ if postsave_render result
20
+ # rendered - no redirect
21
+ elsif result
22
+ render_http_success
23
+ else
24
+ render_http_failure
25
+ end
26
+ end
27
+ end
28
+
29
+ def render_for_safe_actions result = true, opts = {}
30
+ if request.xhr?
31
+ if params[:attribute]
32
+ render_attribute opts
33
+ elsif params[:display_as]
34
+ render_as
35
+ else
36
+ respond_to {|format|
37
+ format.html { render :template => "#{controller_name}/#{action_name}", :layout => false }
38
+ format.xml
39
+ format.js
40
+ }
41
+ end
42
+ else
43
+ respond_to do |format|
44
+ format.html
45
+ format.xml { render :xml => @record }
46
+ format.js
47
+ end unless rendered_or_redirected?
48
+ end
49
+ end
50
+
51
+ def render_http_success
52
+ flash[:notice] = "#{mdl} was successfully #{@record.new_or_deleted_before_save? ? 'created' : 'updated'}."
53
+ respond_to do |format|
54
+ format.html {
55
+ default_redirect_path = postsave_redirect || nested_path_for(inline_createable_resource? ? mdl : @entity)
56
+
57
+ if params[:redirect] == 'back'
58
+ redirect_back_or default_redirect_path
59
+ else
60
+ redirect_to default_redirect_path
61
+ end
62
+ }
63
+ format.xml {
64
+ if @record.new_or_deleted_before_save?
65
+ render :xml => @record, :status => :created, :location => @record
66
+ else # update
67
+ head :ok
68
+ end
69
+ }
70
+ format.js
71
+ format.json { render :json => {:result => 'success'}.to_json }
72
+ end
73
+ end
74
+
75
+ def render_http_failure
76
+ respond_to do |format|
77
+ format.html {
78
+ if inline_createable_resource?
79
+ tasks_for_index
80
+ render :action => :index
81
+ else
82
+ render :action => (@record.new_record? ? 'new' : 'edit')
83
+ end
84
+ }
85
+ format.xml { render :xml => @record.errors, :status => :unprocessable_entity }
86
+ format.js
87
+ format.json { render :json => {:result => 'failure'}.to_json }
88
+ end
89
+ end
90
+
91
+ def render_attribute opts = {}
92
+ render :partial => "restful/attribute", :locals => {
93
+ :record => @record,
94
+ :attribute => params[:attribute],
95
+ :editable => opts[:editable],
96
+ :editing => editing?(@record)
97
+ }
98
+ end
99
+
100
+ def render_for_destroy success, opts = {}
101
+ if request.xhr?
102
+ render :partial => "shared/#{action_name}", :locals => { :record => @record }
103
+ else
104
+ respond_to do |format|
105
+ format.html { redirect_to postdestroy_redirect || nested_path_for(@record.class) }
106
+ format.xml { head :ok }
107
+ format.js { render :partial => "shared/#{action_name}", :locals => {:record => @record} }
108
+ end
109
+ end
110
+ end
111
+
112
+ end
113
+ end
114
+ end