benhoskings-hammock 0.2.4

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 (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