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,125 @@
1
+ module Hammock
2
+ # TODO This file is horribly non-DRY.
3
+ module CannedScopes
4
+ MixInto = ActiveRecord::Base
5
+
6
+ # TODO Put this somewhere better.
7
+ StandardVerbs = [:read, :write, :index, :create]
8
+
9
+ def self.included base
10
+ base.send :include, InstanceMethods
11
+ base.send :extend, ClassMethods
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ protected
17
+
18
+ def public_resource
19
+ public_resource_for *StandardVerbs
20
+ end
21
+
22
+ def authed_resource
23
+ authed_resource_for *StandardVerbs
24
+ end
25
+
26
+ def creator_resource
27
+ creator_resource_for *StandardVerbs
28
+ end
29
+
30
+ def partitioned_resource
31
+ partitioned_resource_for *StandardVerbs
32
+ end
33
+
34
+ def creator_resource_for *verbs
35
+ metaclass.instance_eval {
36
+ verbs.each {|verb|
37
+ send :define_method, "#{verb}_scope_for" do |account|
38
+ creator_scope account
39
+ end
40
+ }
41
+ }
42
+ define_createable :creator_scope if verbs.include?(:create)
43
+ export_scopes *verbs
44
+ end
45
+
46
+ def public_resource_for *verbs
47
+ metaclass.instance_eval {
48
+ verbs.each {|verb|
49
+ send :define_method, "#{verb}_scope" do
50
+ public_scope nil
51
+ end
52
+ }
53
+ }
54
+ define_createable :public_scope if verbs.include?(:create)
55
+ export_scopes *verbs
56
+ end
57
+
58
+ def authed_resource_for *verbs
59
+ metaclass.instance_eval {
60
+ verbs.each {|verb|
61
+ send :define_method, "#{verb}_scope_for" do |account|
62
+ authed_scope account
63
+ end
64
+ }
65
+ }
66
+ define_createable :authed_scope if verbs.include?(:create)
67
+ export_scopes *verbs
68
+ end
69
+
70
+ def partitioned_resource_for *verbs
71
+ metaclass.instance_eval {
72
+ verbs.each {|verb|
73
+ send :define_method, "#{verb}_scope_for" do |account|
74
+ partitioned_scope account
75
+ end
76
+ }
77
+ }
78
+ define_createable :partitioned_scope if verbs.include?(:create)
79
+ export_scopes *verbs
80
+ end
81
+
82
+ def define_createable scope_name
83
+ instance_eval {
84
+ send :define_method, :createable_by? do |account|
85
+ self.class.send scope_name, account
86
+ end
87
+ }
88
+ end
89
+
90
+ def public_scope account
91
+ if sqlite?
92
+ lambda {|record| 1 }
93
+ else
94
+ lambda {|record| true }
95
+ end
96
+ end
97
+
98
+ def authed_scope account
99
+ has_account = !account.nil?
100
+ lambda {|record| has_account }
101
+ end
102
+
103
+ def creator_scope account
104
+ lambda {|record| record.creator_id == account.id }
105
+ end
106
+
107
+ def partitioned_scope account
108
+ lambda {|record| record.id == account.id }
109
+ end
110
+
111
+ def empty_scope
112
+ if sqlite?
113
+ lambda {|record| 0 } # TODO check what this should be
114
+ else
115
+ lambda {|record| false }
116
+ end
117
+ end
118
+
119
+ end
120
+
121
+ module InstanceMethods
122
+
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,9 @@
1
+ module Hammock
2
+ PleaseFileABug = "Ack, that shouldn't have happened. Please email the full stacktrace to <ben@hoskings.net>, so the problem you uncovered can be addressed. Thanks!"
3
+
4
+ module Constants
5
+
6
+ ImpliedUnsafeActions = [:new, :edit, :destroy]
7
+
8
+ end
9
+ end
@@ -0,0 +1,52 @@
1
+ module Hammock
2
+ module ControllerAttributes
3
+ def self.included base # :nodoc:
4
+ base.send :include, InstanceMethods
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ # When +inline_create+ is specified for a controller, the +index+ page will have the ability to directly create new resources, just as the +new+ page normally can.
10
+ #
11
+ # To use +inline_create+, refactor the relevant contents of your +new+ view into a partial and render it in an appropriate place within the +index+ view.
12
+ #
13
+ # A successful +create+ will redirect to the +show+ action for the new record, and a failed +create+ will re-render the +index+ action with a populated form, in the same way the +new+ action would normally be rendered in the event of a failed +create+.
14
+ def inline_create
15
+ write_inheritable_attribute :inline_create, true
16
+ end
17
+
18
+ # When +find_on_create+ is specified for a controller, attempts to +create+ new records will first check to see if an identical record already exists. If such a record is found, it is returned and the create is never attempted.
19
+ #
20
+ # This is useful for the management of administrative records like memberships or friendships, where the user may attempt to create a new record using some unique identifier like an email address. For such a resource, a pre-existing record should not be considered a failure, as would otherwise be triggered by uniqueness checks on the model.
21
+ def find_on_create
22
+ write_inheritable_attribute :find_on_create, true
23
+ end
24
+
25
+ def paginate_by per_page
26
+ write_inheritable_attribute :pagination_enabled, true
27
+ mdl.metaclass.instance_eval do
28
+ define_method :per_page do
29
+ per_page
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ module InstanceMethods
36
+
37
+ private
38
+
39
+ def inline_createable_resource?
40
+ self.class.read_inheritable_attribute :inline_create
41
+ end
42
+
43
+ def findable_on_create?
44
+ self.class.read_inheritable_attribute :find_on_create
45
+ end
46
+
47
+ def pagination_enabled?
48
+ self.class.read_inheritable_attribute :pagination_enabled
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,74 @@
1
+ module Hammock
2
+ module ExportScope
3
+ MixInto = ActiveRecord::Base
4
+
5
+ def self.included base
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def has_public_scope? scope_name
13
+ "#{scope_name}able" if respond_to? "#{scope_name}_scope"
14
+ end
15
+
16
+ def has_account_scope? scope_name
17
+ "#{scope_name}able_by" if respond_to? "#{scope_name}_scope_for"
18
+ end
19
+
20
+ def export_scopes *verbs
21
+ verbs.discard(:create).each {|verb| export_scope verb }
22
+ end
23
+
24
+ def export_scope verb
25
+ verbable = "#{verb}able"
26
+
27
+ metaclass.instance_eval {
28
+ # Model.verbable_by: returns all records that are verbable by account.
29
+ define_method "#{verbable}_by" do |account|
30
+ if !account.nil? && respond_to?("#{verb}_scope_for")
31
+ select &send("#{verb}_scope_for", account)
32
+ elsif respond_to?("#{verb}_scope")
33
+ select &send("#{verb}_scope")
34
+ else
35
+ log "No #{verb} scopes available."
36
+ nil
37
+ end
38
+ end
39
+
40
+ # Model.verbable: returns all records that are verbable by anonymous users.
41
+ define_method verbable do
42
+ send "#{verbable}_by", nil
43
+ end
44
+ }
45
+
46
+ # Model#verbable_by?: returns whether this record is verbable by account.
47
+ define_method "#{verbable}_by?" do |account|
48
+ if !account.nil? && self.class.respond_to?("#{verb}_scope_for")
49
+ self.class.send("#{verb}_scope_for", account).call(self)
50
+ elsif self.class.respond_to?("#{verb}_scope")
51
+ self.class.send("#{verb}_scope").call(self)
52
+ else
53
+ log "No #{verb} scopes available, returning false."
54
+ false
55
+ end
56
+ end
57
+
58
+ # Model#verbable?: returns whether this record is verbable by anonymous users.
59
+ define_method "#{verbable}?" do
60
+ send "#{verbable}_by?", nil
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ module InstanceMethods
67
+
68
+ def createable_by? account
69
+ false
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,47 @@
1
+ module Hammock
2
+ module HamlinkTo
3
+ MixInto = ActionView::Base
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ end
12
+
13
+ module InstanceMethods
14
+
15
+ # Generate a restful link to verb the provided resources if the request would be allowed under the current scope.
16
+ # If the scope would not allow the specified request, the link is ommitted entirely from the page.
17
+ #
18
+ # Use this method to render edit and delete links, and anything else that is only available to certain users or under certain conditions.
19
+ def hamlink_to *args
20
+ opts = args.extract_options!
21
+ verb = args.first if args.first.is_a?(Symbol)
22
+ entity = args.last
23
+
24
+ if can_verb_entity?(verb, entity)
25
+ route = route_for *args.push(opts.dragnet(:nest, :format))
26
+
27
+ # opts[:class] = ['current', opts[:class]].squash.join(' ') if opts[:indicate_current] && (route == controller.current_route)
28
+ opts[:class] = [link_class_for(route.verb, entity), opts[:class]].squash.join(' ')
29
+
30
+ text = opts.delete(:text) || opts.delete(:text_or_else)
31
+
32
+ if text.is_a?(Symbol)
33
+ text = entity.send(text)
34
+ end
35
+
36
+ link_to(text || route.verb,
37
+ route.path(opts.delete(:params)),
38
+ opts.merge(:method => (route.http_method unless route.get?))
39
+ )
40
+ else
41
+ opts[:else] || opts[:text_or_else]
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,63 @@
1
+ module Hammock
2
+ module JavascriptBuffer
3
+ MixInto = ActionView::Base
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ end
12
+
13
+ module InstanceMethods
14
+
15
+ # Add +snippet+ to the request's domready javascript cache.
16
+ #
17
+ # The contents of this cache can be rendered into a jQuery <tt>$(function() { ... })</tt> block within a <tt>\<script type="text/javascript"></tt> block by calling <tt>javascript_for_page</tt> within the \<head> of the layout.
18
+ def append_javascript snippet
19
+ # TODO This should be an array of strings.
20
+ @_domready_javascript ||= ''
21
+ @_domready_javascript << snippet.strip.end_with(';') << "\n\n" unless snippet.nil?
22
+ end
23
+
24
+ # Add +snippet+ to the request's toplevel javascript cache.
25
+ #
26
+ # The contents of this cache can be rendered into a <tt>\<script type="text/javascript"></tt> block by calling <tt>javascript_for_page</tt> within the \<head> of the layout.
27
+ def append_toplevel_javascript snippet
28
+ @_toplevel_javascript ||= ''
29
+ @_toplevel_javascript << snippet.strip.end_with(';') << "\n\n" unless snippet.nil?
30
+ end
31
+
32
+ # Render the snippets cached by +append_javascript+ and +append_toplevel_javascript+ within a <tt>\<script type="text/javascript"></tt> tag.
33
+ #
34
+ # This should be called somewhere within the \<head> in your layout.
35
+ def javascript_for_page
36
+ javascript_tag %Q{
37
+ #{@_toplevel_javascript}
38
+
39
+ (jQuery)(function() {
40
+ #{@_domready_javascript}
41
+ });
42
+ }
43
+ end
44
+
45
+ # If the current request is XHR, render all cached javascript as +javascript_for_page+ would and clear the request's javascript cache.
46
+ #
47
+ # The purpose of this method is for rendering javascript into partials that form XHR responses, without causing duplicate javascript to be rendered by nested partials multiply calling this method.
48
+ def javascript_for_ajax_response
49
+ # TODO this should be called from outside the partials somewhere, once only
50
+ if request.xhr?
51
+ js = javascript_for_page
52
+ clear_js_caches
53
+ js
54
+ end
55
+ end
56
+
57
+ def clear_js_caches
58
+ @_domready_javascript = @_toplevel_javascript = nil
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,98 @@
1
+ module Hammock
2
+ module Logging
3
+ MixInto = ActionController::Base
4
+
5
+ def self.included base
6
+ base.send :include, Methods
7
+ base.send :extend, Methods
8
+
9
+ base.class_eval {
10
+ helper_method :log
11
+ }
12
+ end
13
+
14
+ module Methods
15
+
16
+ def log_hit
17
+ log_concise [
18
+ request.remote_ip.colorize('green'),
19
+ (@current_site.subdomain unless @current_site.nil?),
20
+ (session.nil? ? 'nil' : ('...' + session.session_id[-8, 8])),
21
+ (current_user.nil? ? "unauthed" : "Account<#{current_user.id}> #{current_user.name}").colorize('green'),
22
+ headers['Status'],
23
+ log_hit_request_info,
24
+ log_hit_route_info
25
+ ].squash.join(' | ')
26
+ end
27
+
28
+ def log_hit_request_info
29
+ (request.xhr? ? 'XHR/' : '') +
30
+ (params[:_method] || request.method).to_s.upcase +
31
+ ' ' +
32
+ request.request_uri.colorize('grey', '?')
33
+ end
34
+
35
+ def log_hit_route_info
36
+ params[:controller] +
37
+ '#' +
38
+ params[:action] +
39
+ ' ' +
40
+ params.discard(:controller, :action).inspect.gsub("\n", '\n').colorize('grey')
41
+ end
42
+
43
+ def log_concise msg, report = false
44
+ buf = "#{Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')} | #{msg}\n"
45
+ path = File.join RAILS_ROOT, 'log', rails_env
46
+
47
+ File.open("#{path}.concise.log", 'a') {|f| f << buf }
48
+ File.open("#{path}.report.log", 'a') {|f| f << buf } if report
49
+
50
+ nil
51
+ end
52
+
53
+ def report *args
54
+ opts = args.extract_options!
55
+ log *(args << opts.merge(:report => true, :skip => (opts[:skip] || 0) + 1))
56
+ log caller.remove_framework_backtrace.join("\n")
57
+ end
58
+
59
+ def dlog *args
60
+ unless production?
61
+ opts = args.extract_options!
62
+ log *(args << opts.merge(:skip => (opts[:skip] || 0) + 1))
63
+ end
64
+ end
65
+
66
+ def log_fail *args
67
+ log *(args << opts.merge(:skip => (opts[:skip] || 0) + 1))
68
+ false
69
+ end
70
+
71
+ def log *args
72
+ opts = {
73
+ :skip => 0
74
+ }.merge(args.extract_options!)
75
+
76
+ msg = if opts[:error]
77
+ "#{ErrorPrefix}: #{opts[:error]}"
78
+ elsif args.first.is_a? String
79
+ args.first
80
+ elsif args.all? {|i| i.is_a?(ActiveRecord::Base) }
81
+ @errorModels = args unless opts[:errorModels] == false
82
+ args.map {|record| "#{record.inspect}: #{record.errors.full_messages.inspect}" }.join(', ')
83
+ else
84
+ args.map(&:inspect).join(', ')
85
+ end
86
+
87
+ msg.colorize!('on red') if opts[:error] || opts[:report]
88
+
89
+ callpoint = caller[opts[:skip]].sub(rails_root.end_with('/'), '')
90
+ entry = "#{callpoint}#{msg.blank? ? (opts[:report] ? ' <-- something broke here' : '.') : ' | '}#{msg}"
91
+
92
+ logger.send opts[:error].blank? ? :info : :error, entry # Write to the Rails log
93
+ log_concise entry, opts[:report] # Also write to the concise log
94
+ end
95
+
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,53 @@
1
+ module Hammock
2
+ module ModelAttributes
3
+ MixInto = ActiveRecord::Base
4
+
5
+ def self.included base
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def route_by attribute
13
+ write_inheritable_attribute :routing_attribute, attribute.to_sym
14
+ define_method :to_param do
15
+ send self.class.routing_attribute
16
+ end
17
+ end
18
+ def nest_within *attributes
19
+ write_inheritable_attribute :nestable_routing_resources, attributes
20
+ end
21
+ def has_defaults attrs
22
+ write_inheritable_attribute :default_attributes, (default_attributes || {}).merge(attrs)
23
+ end
24
+ def attr_accessible_on_create *attributes
25
+ write_inheritable_attribute :attr_accessible_on_create, Set.new(attributes.map(&:to_s)) + (accessible_attributes_on_create || [])
26
+ end
27
+ def attr_accessible_on_update *attributes
28
+ write_inheritable_attribute :attr_accessible_on_update, Set.new(attributes.map(&:to_s)) + (accessible_attributes_on_update || [])
29
+ end
30
+
31
+ def routing_attribute
32
+ read_inheritable_attribute(:routing_attribute) || :id
33
+ end
34
+ def nestable_routing_resources
35
+ read_inheritable_attribute(:nestable_routing_resources)
36
+ end
37
+ def default_attributes
38
+ read_inheritable_attribute(:default_attributes) || {}
39
+ end
40
+ def accessible_attributes_on_create
41
+ read_inheritable_attribute :attr_accessible_on_create
42
+ end
43
+ def accessible_attributes_on_update
44
+ read_inheritable_attribute :attr_accessible_on_update
45
+ end
46
+
47
+ end
48
+
49
+ module InstanceMethods
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,30 @@
1
+ module Hammock
2
+ module ModelLogging
3
+ MixInto = ActiveRecord::Base
4
+
5
+ def self.included base
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ include Hammock::Utils::Methods
12
+ include Hammock::Logging::Methods
13
+ end
14
+
15
+ module InstanceMethods
16
+ include Hammock::Utils::Methods
17
+ include Hammock::Logging::Methods
18
+
19
+ def log_with_model *args
20
+ opts = args.extract_options!
21
+
22
+ message = "#{self.class}<#{self.id}>#{(' | ' + args.shift) if args.first.is_a?(String)}"
23
+
24
+ log_without_model *args.unshift(message).push(opts.merge(:skip => (opts[:skip] || 0) + 1))
25
+ end
26
+ alias_method_chain :log, :model
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ module Hammock
2
+ module ActionControllerPatches
3
+ MixInto = ActionController::Rescue
4
+
5
+ def self.included base
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+
9
+ # base.class_eval {
10
+ # alias_method_chain :clean_backtrace, :truncation
11
+ # }
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ end
17
+
18
+ module InstanceMethods
19
+
20
+ private
21
+
22
+ def clean_backtrace_with_truncation exception
23
+ if backtrace = clean_backtrace_without_truncation(exception)
24
+ backtrace.take_while {|line|
25
+ line['perform_action_without_filters'].nil?
26
+ }.push("... and so on")
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end