benhoskings-hammock 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/LICENSE +24 -0
  2. data/Manifest.txt +42 -0
  3. data/README.rdoc +105 -0
  4. data/Rakefile +27 -0
  5. data/lib/hammock.rb +25 -0
  6. data/lib/hammock/ajaxinate.rb +152 -0
  7. data/lib/hammock/callbacks.rb +107 -0
  8. data/lib/hammock/canned_scopes.rb +121 -0
  9. data/lib/hammock/constants.rb +7 -0
  10. data/lib/hammock/controller_attributes.rb +66 -0
  11. data/lib/hammock/export_scope.rb +74 -0
  12. data/lib/hammock/hamlink_to.rb +47 -0
  13. data/lib/hammock/javascript_buffer.rb +63 -0
  14. data/lib/hammock/logging.rb +98 -0
  15. data/lib/hammock/model_attributes.rb +38 -0
  16. data/lib/hammock/model_logging.rb +30 -0
  17. data/lib/hammock/monkey_patches/action_pack.rb +32 -0
  18. data/lib/hammock/monkey_patches/active_record.rb +227 -0
  19. data/lib/hammock/monkey_patches/array.rb +73 -0
  20. data/lib/hammock/monkey_patches/hash.rb +49 -0
  21. data/lib/hammock/monkey_patches/logger.rb +28 -0
  22. data/lib/hammock/monkey_patches/module.rb +27 -0
  23. data/lib/hammock/monkey_patches/numeric.rb +25 -0
  24. data/lib/hammock/monkey_patches/object.rb +61 -0
  25. data/lib/hammock/monkey_patches/route_set.rb +200 -0
  26. data/lib/hammock/monkey_patches/string.rb +197 -0
  27. data/lib/hammock/overrides.rb +32 -0
  28. data/lib/hammock/resource_mapping_hooks.rb +28 -0
  29. data/lib/hammock/resource_retrieval.rb +115 -0
  30. data/lib/hammock/restful_actions.rb +170 -0
  31. data/lib/hammock/restful_rendering.rb +114 -0
  32. data/lib/hammock/restful_support.rb +167 -0
  33. data/lib/hammock/route_drawing_hooks.rb +22 -0
  34. data/lib/hammock/route_for.rb +58 -0
  35. data/lib/hammock/scope.rb +120 -0
  36. data/lib/hammock/suggest.rb +36 -0
  37. data/lib/hammock/utils.rb +42 -0
  38. data/misc/scaffold.txt +83 -0
  39. data/misc/template.rb +17 -0
  40. data/tasks/hammock_tasks.rake +5 -0
  41. data/test/hammock_test.rb +8 -0
  42. metadata +129 -0
@@ -0,0 +1,32 @@
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 postsave_render result
19
+ nil
20
+ end
21
+
22
+ def postsave_redirect
23
+ nil
24
+ end
25
+
26
+ def postdestroy_redirect
27
+ nil
28
+ end
29
+
30
+ end
31
+ end
32
+ 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,115 @@
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
+ assign_entity scope
44
+ end
45
+ end
46
+
47
+ def retrieve_record finder
48
+ if (scope = current_scope).nil?
49
+
50
+ else
51
+ record = scope.send finder, :first, :conditions => {find_column_name => params[:id]}
52
+ record || required_callback(:after_failed_find)
53
+ end
54
+ end
55
+
56
+ def escort reason
57
+ if rendered_or_redirected?
58
+ # lol
59
+ elsif request.xhr?
60
+ # TODO bad request might only be appropriate for invalid requests, as opposed to just an auth failure.
61
+ escort_for_bad_request
62
+ elsif :relogin == reason
63
+ 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.
64
+ redirect_to returning_login_path
65
+ elsif :readonly == reason
66
+ escort_for_read_only
67
+ elsif :unauthed == reason
68
+ escort_for_403
69
+ elsif @current_account.nil? && account_verb_scope?
70
+ escort_for_login
71
+ else
72
+ escort_for_404
73
+ end
74
+ false
75
+ end
76
+
77
+
78
+ private
79
+
80
+ def returning_login_path
81
+ session[:path_after_login] = request.request_uri
82
+ login_path
83
+ end
84
+
85
+ def escort_for_bad_request
86
+ log
87
+ render :nothing => true, :status => 400
88
+ end
89
+ def escort_for_read_only
90
+ log
91
+ redirect_to :action => :show
92
+ end
93
+ def escort_for_login
94
+ log
95
+ # render :partial => 'login/account', :status => 401 # unauthorized
96
+ redirect_to returning_login_path
97
+ end
98
+ def escort_for_404
99
+ if partial_exists? 'shared/status_404'
100
+ render :partial => 'shared/status_404', :layout => true
101
+ else
102
+ render :file => File.join(RAILS_ROOT, 'public/404.html'), :status => 404
103
+ end
104
+ end
105
+ def escort_for_403
106
+ if partial_exists? 'shared/status_404'
107
+ render :partial => 'shared/status_404', :layout => true
108
+ else
109
+ render :file => File.join(RAILS_ROOT, 'public/404.html'), :status => 403
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,170 @@
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.suggest fields, @queries
122
+ end
123
+
124
+ if @results.nil?
125
+ escort_for_bad_request
126
+ else
127
+ callback(:after_suggest)
128
+ render :action => "suggest_#{fields.join(',')}", :layout => false
129
+ end
130
+ end
131
+
132
+
133
+ private
134
+
135
+ def tasks_for_index
136
+ retrieve_resource and callback(:before_index) and (!inline_createable_resource? || tasks_for_new)
137
+ end
138
+
139
+ def tasks_for_new
140
+ callback(:before_modify) and callback(:before_new) if assign_createable
141
+ end
142
+
143
+ def find_record_on_create
144
+ if findable_on_create?
145
+ if record = nest_scope.find(:first, :conditions => params_for(mdl.symbolize))
146
+ log "suitable record already exists: #{record}"
147
+ assign_entity record
148
+ else
149
+ log "couldn't find a suitable record, proceeding with creation."
150
+ end
151
+ end
152
+ end
153
+
154
+ def save_record
155
+ verb = @record.new_record? ? 'create' : 'update'
156
+ if callback("before_#{verb}") and callback(:before_save) and save
157
+ callback("after_#{verb}") and callback(:after_save)
158
+ else
159
+ log "#{mdl} errors: " + @record.errors.full_messages.join(', ')
160
+ callback("after_failed_#{verb}") and callback(:after_failed_save) and false
161
+ end
162
+ end
163
+
164
+ def save
165
+ @record.save
166
+ end
167
+
168
+ end
169
+ end
170
+ 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