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.
- data/LICENSE +24 -0
- data/Manifest.txt +42 -0
- data/README.rdoc +105 -0
- data/Rakefile +27 -0
- data/lib/hammock.rb +25 -0
- data/lib/hammock/ajaxinate.rb +152 -0
- data/lib/hammock/callbacks.rb +107 -0
- data/lib/hammock/canned_scopes.rb +121 -0
- data/lib/hammock/constants.rb +7 -0
- data/lib/hammock/controller_attributes.rb +66 -0
- data/lib/hammock/export_scope.rb +74 -0
- data/lib/hammock/hamlink_to.rb +47 -0
- data/lib/hammock/javascript_buffer.rb +63 -0
- data/lib/hammock/logging.rb +98 -0
- data/lib/hammock/model_attributes.rb +38 -0
- data/lib/hammock/model_logging.rb +30 -0
- data/lib/hammock/monkey_patches/action_pack.rb +32 -0
- data/lib/hammock/monkey_patches/active_record.rb +227 -0
- data/lib/hammock/monkey_patches/array.rb +73 -0
- data/lib/hammock/monkey_patches/hash.rb +49 -0
- data/lib/hammock/monkey_patches/logger.rb +28 -0
- data/lib/hammock/monkey_patches/module.rb +27 -0
- data/lib/hammock/monkey_patches/numeric.rb +25 -0
- data/lib/hammock/monkey_patches/object.rb +61 -0
- data/lib/hammock/monkey_patches/route_set.rb +200 -0
- data/lib/hammock/monkey_patches/string.rb +197 -0
- data/lib/hammock/overrides.rb +32 -0
- data/lib/hammock/resource_mapping_hooks.rb +28 -0
- data/lib/hammock/resource_retrieval.rb +115 -0
- data/lib/hammock/restful_actions.rb +170 -0
- data/lib/hammock/restful_rendering.rb +114 -0
- data/lib/hammock/restful_support.rb +167 -0
- data/lib/hammock/route_drawing_hooks.rb +22 -0
- data/lib/hammock/route_for.rb +58 -0
- data/lib/hammock/scope.rb +120 -0
- data/lib/hammock/suggest.rb +36 -0
- data/lib/hammock/utils.rb +42 -0
- data/misc/scaffold.txt +83 -0
- data/misc/template.rb +17 -0
- data/tasks/hammock_tasks.rake +5 -0
- data/test/hammock_test.rb +8 -0
- 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
|