hammock 0.2.11.2

Sign up to get free protection for your applications and to get access to all the features.
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
data/History.txt ADDED
@@ -0,0 +1,67 @@
1
+ == 0.2.11.2 2009-03-24
2
+ Loosened Rails dependency from ~> 2.2.2 to 2.2.
3
+
4
+
5
+ == 0.2.11.1 2009-03-24
6
+ Fixed 'railslol' typo in Rakefile preventing installation. (lol)
7
+
8
+
9
+ == 0.2.11 2009-03-23
10
+ Added hammock include hook to hammock.rb, to sidestep changed ApplicationController load behaviour in Rails 2.3.
11
+ Added pagination support to the index route.
12
+ Changed #mdl and #mdl_name implementations to class methods, and re-aliased them as instance methods.
13
+
14
+
15
+ == 0.2.10 2009-03-17
16
+ Added AR::Base.suggest_scope for narrowing the scope in the suggest action.
17
+ Changed identifier in AR::Base#concise_inspect from #id to #to_param.
18
+ Fixed raise condition for entities arg to RouteNode#for, and improved exception message.
19
+ Changed squash! to compact! in route_for, to only remove nil elements (and not, say, models with no records that respond true to blank?).
20
+
21
+
22
+ == 0.2.9 2009-03-13
23
+ Updated partial_exists? to default format to the current request format instead of html, and accept format instead of extension as a parameter.
24
+
25
+
26
+ == 0.2.8 2009-03-10
27
+ Renamed @current_account to current_user.
28
+ Removed AC::Base#nestable_by declaration in favour of the new approach involving AC::Base#route_by and #nest_within, along with the corresponding reader.
29
+ Added #set_new_or_deleted_before_save controller method, and before_create and before_undestroy hooks.
30
+ Revert "Removed unneeded param to specify the finder method in retrieve_record." - the param was actually needed for #find_deleted_record.
31
+
32
+
33
+ == 0.2.7 2009-03-05
34
+ hamlink_to with implied verbs no longer raises in route_for.
35
+
36
+
37
+ == 0.2.6 2009-03-05
38
+ Access current_nested_records in nested_route_for through the method instead of directly.
39
+ Replaced #assign_nestable_resources with #assign_nesting_entities, to separately kick and assign an ivar for each of the current rou
40
+ Added nesting_scope_list to encapsulate what @hammock_nesting_scopes was set to before.
41
+ Updated nested_within? to use methods instead of ivars, removed duping from current_nested_[record,resource]s, and added add_nested_en
42
+ Updated empty_scope for sqlite, to bring it in line with public_scope.
43
+ Updated current_scope to fail gracefully if current_verb_scope (renamed from verb_scope along with [current_]nest_scope) returns nil.
44
+ Replaced RouteNode#nesting_scope_for with nesting_scope_list_for, and changed nest_scope call appropriately. The scopes are returned s
45
+ Replaced AR::Base#id_or_description with #description, added .description, and use in link_class_for for more consistent class names.
46
+ Split Hammock::Callback (previously HammockCallback) out into its own file.
47
+ Split RouteNode and RouteStep (previously HammockResource and HammockRoutePiece) out into their own files.
48
+ Reject Hammock:: classes from hammock load. Classes are modules. Who knew?
49
+ Changed inheritable_attribute keys from strings to symbols.
50
+ Caching the model within HammockResource was causing stale class fails in development mode.
51
+ retrieve_record uses mdl.routing_attribute now instead of the old find_column_name.
52
+ Default routing_attribute to :id.
53
+ Fixed the 'no valid AR reflections' message to be a user error instead of an internal hammock error (they just need to add a reflection)
54
+ HammockRoutePiece erroneously had a root? check instead of a parent.nil? check - fixed.
55
+ Only set creator_id if someone is logged in.
56
+ Updated #nest_scope to use #nesting_scope_for instead of manually recursively selecting within a single context, and updated #curren
57
+ Re-enabled logging of Ambition queries as they are assigned as entities.
58
+ Replaced the questionable #current_route with #current_hammock_resource, which uses HammockResource#base_for.
59
+ Added HammockResource#base_for, #nesting_scope_for and #nesting_scope_segment_for.
60
+ Added HammockResource#determine_routing_parent, to determine which ActiveRecord reflection to use to generate joins for route queryi
61
+ Added Hash#selekt, to sidestep the ruby-1.8/1.9 #select changes.
62
+ Commented current_route-dependent code in hamlink_to until that is sorted out.
63
+ Test if obj is defined before attempting to select .spinner within it.
64
+
65
+ == 0.2.4 2009-02-25
66
+
67
+ * Initial gem release
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2008 Ben Hoskings <ben@hoskings.net>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of Ben Hoskings nor the names of any of the software's
12
+ contributors may be used to endorse or promote products derived from
13
+ this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
16
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Manifest.txt ADDED
@@ -0,0 +1,46 @@
1
+ History.txt
2
+ LICENSE
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ hammock.gemspec
7
+ lib/hammock.rb
8
+ lib/hammock/ajaxinate.rb
9
+ lib/hammock/callback.rb
10
+ lib/hammock/callbacks.rb
11
+ lib/hammock/canned_scopes.rb
12
+ lib/hammock/constants.rb
13
+ lib/hammock/controller_attributes.rb
14
+ lib/hammock/export_scope.rb
15
+ lib/hammock/hamlink_to.rb
16
+ lib/hammock/javascript_buffer.rb
17
+ lib/hammock/logging.rb
18
+ lib/hammock/model_attributes.rb
19
+ lib/hammock/model_logging.rb
20
+ lib/hammock/monkey_patches/action_pack.rb
21
+ lib/hammock/monkey_patches/active_record.rb
22
+ lib/hammock/monkey_patches/array.rb
23
+ lib/hammock/monkey_patches/hash.rb
24
+ lib/hammock/monkey_patches/logger.rb
25
+ lib/hammock/monkey_patches/module.rb
26
+ lib/hammock/monkey_patches/numeric.rb
27
+ lib/hammock/monkey_patches/object.rb
28
+ lib/hammock/monkey_patches/route_set.rb
29
+ lib/hammock/monkey_patches/string.rb
30
+ lib/hammock/overrides.rb
31
+ lib/hammock/resource_mapping_hooks.rb
32
+ lib/hammock/resource_retrieval.rb
33
+ lib/hammock/restful_actions.rb
34
+ lib/hammock/restful_rendering.rb
35
+ lib/hammock/restful_support.rb
36
+ lib/hammock/route_drawing_hooks.rb
37
+ lib/hammock/route_for.rb
38
+ lib/hammock/route_node.rb
39
+ lib/hammock/route_step.rb
40
+ lib/hammock/scope.rb
41
+ lib/hammock/suggest.rb
42
+ lib/hammock/utils.rb
43
+ misc/scaffold.txt
44
+ misc/template.rb
45
+ tasks/hammock_tasks.rake
46
+ test/hammock_test.rb
data/README.rdoc ADDED
@@ -0,0 +1,105 @@
1
+ = hammock
2
+
3
+ http://github.com/benhoskings/hammock
4
+
5
+
6
+ == DESCRIPTION:
7
+
8
+ Hammock is a Rails plugin that eliminates redundant code in a very RESTful manner. It does this in lots in lots of different places, but in one manner: it encourages specification in place of implementation.
9
+
10
+
11
+ Hammock enforces RESTful resource access by abstracting actions away from the controller in favour of a clean, model-like callback system.
12
+
13
+ Hammock tackles the hard and soft sides of security at once with a scoping security system on your models. Specify who can verb what resources under what conditions once, and everything else - the actual security, link generation, index filtering - just happens.
14
+
15
+ Hammock inspects your routes and resources to generate a routing tree for each resource. Parent resources in a nested route are handled transparently at every point - record retrieval, creation, and linking.
16
+
17
+ It makes more sense when you see how it works though, so check out the screencast!
18
+
19
+
20
+ == REQUIREMENTS:
21
+
22
+ benhoskings-ambition
23
+ benhoskings-ambitious-activerecord
24
+
25
+
26
+ == INSTALL:
27
+
28
+ sudo gem install benhoskings-hammock --source http://gems.github.com
29
+
30
+ class ApplicationController
31
+ include Hammock
32
+ ...
33
+
34
+
35
+ == LICENSE:
36
+
37
+ Hammock is licensed under the BSD license, which can be found in full in the LICENSE file.
38
+
39
+
40
+ == SYNOPSIS
41
+
42
+ At the moment, you can do this with Hammock:
43
+
44
+ class ApplicationController < ActionController::Base
45
+ include Hammock
46
+ end
47
+
48
+ class BeersController < ApplicationController
49
+ end
50
+
51
+ class Person < ActiveRecord::Base
52
+ end
53
+
54
+ class Beer < ActiveRecord::Base
55
+ belongs_to :creator, :class_name => 'Person'
56
+ belongs_to :recipient, :class_name => 'Person'
57
+
58
+ def read_scope_for account
59
+ proc {|beer| beer.creator_id == account.id || beer.recipient_id == account.id }
60
+ end
61
+ export_scope :read
62
+ export_scope :read, :as => :index
63
+
64
+ def write_scope_for account
65
+ proc {|beer| record.creator_id == account.id }
66
+ end
67
+ export_scope :write
68
+ end
69
+
70
+ <% @beers.each do |beer| %>
71
+ From <%= beer.creator.name %> to <%= beer.recipient.name %>, <%= beer.reason %>, rated <%= beer.rating %>
72
+ <%= hamlink_to :edit, beer %>
73
+ <% end %>
74
+
75
+ The scope methods above require just one thing -- a context-free proc object that takes an ActiveRecord record as its argument, and returns true iff that record is within the scope for the specified account. Hammock uses the method (e.g. Beer.read_scope_for) to define resource and record scopes for the model:
76
+
77
+ Beer.readable_by(account): the set of Beer records whose existence can be known by account
78
+ Beer#readable_by?(account): returns true if the existence of this Beer instance can be known by account
79
+
80
+ You define the logic for read, index and write scopes in Beer.[read,index,write]_scope_for, and the rest just works.
81
+
82
+ These scope definitions are exploited extensively, to provide index selection, scoping for record selection, and post-selection object checks.
83
+
84
+ - They provide the conditions that should be applied to retrieve the index of each resource. The scope is used transperently by Hammock on /beers -> BeersController#index, and is available for use through Beer.indexable_by(account).
85
+
86
+ - They provide a scope within which records are searched for on single-record actions. For example, given the request /beers/5 -> BeersController#show{:id => 5}, Rails would generate the following SQL:
87
+
88
+ SELECT * FROM "beers" WHERE (beers."id" = 5) LIMIT 1
89
+
90
+ Hammock uses the conditions specified in Beer.read_scope_for to generate (assuming an account_id of 3):
91
+
92
+ SELECT * FROM "beers" WHERE ((beers.creator_id = 3 OR beers.recipient_id = 3) AND beers."id" = 5) LIMIT 1
93
+
94
+ Hammock uses Beer.read_scope_for on #show, and write_scope_for on #edit, #update and #destroy. These scopes can be accessed as above through Beer.readable_by(account) and Beer.writeable_by(account). This eliminates authorization checks from the action, because if the ID of a Beer is provided that the user doesn't have access to it will fall outside the scope and will not be found in the DB at all.
95
+
96
+ - They are used to discover credentials for already-queried ActiveRecord objects, without touching the database again. Just as Beer.readable_by(account) returns the set of Beer records whose existence can be known by account, @beer.readable_by?(account) returns true iff @beer's existence can be known by account. This is employed by hamlink_to.
97
+
98
+ These three uses of the scope, plus another as-yet unimplemented bit, provide the entire security model of the application.
99
+
100
+ == THE MASTER PLAN
101
+
102
+ Lots of functionality is planned that will take this much further.
103
+
104
+
105
+
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ %w[action_controller].each { |f| require f }
3
+ require File.dirname(__FILE__) + '/lib/hammock'
4
+
5
+ # Generate all the Rake tasks
6
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
7
+ $hoe = Hoe.new('hammock', Hammock::VERSION) do |p|
8
+ p.developer('Ben Hoskings', 'ben@hoskings.net')
9
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
10
+ p.rubyforge_name = p.name
11
+ p.extra_deps = [
12
+ ['rails','~> 2.2'],
13
+ ['benhoskings-ambitious-activerecord','~> 0.1.3.5'],
14
+ ]
15
+ p.extra_dev_deps = [
16
+ ['newgem', ">= #{::Newgem::VERSION}"]
17
+ ]
18
+
19
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
20
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
21
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
22
+ p.rsync_args = '-av --delete --ignore-errors'
23
+ end
24
+
25
+ require 'newgem/tasks' # load /tasks/*.rake
26
+ Dir['tasks/**/*.rake'].each { |t| load t }
27
+
28
+ # TODO - want other tests/tasks run by default? Add them to the list
29
+ # task :default => [:spec, :features]
data/hammock.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{hammock}
5
+ s.version = "0.2.11.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ben Hoskings"]
9
+ s.date = %q{2009-03-24}
10
+ s.description = %q{Hammock is a Rails plugin that eliminates redundant code in a very RESTful manner. It does this in lots in lots of different places, but in one manner: it encourages specification in place of implementation. Hammock enforces RESTful resource access by abstracting actions away from the controller in favour of a clean, model-like callback system. Hammock tackles the hard and soft sides of security at once with a scoping security system on your models. Specify who can verb what resources under what conditions once, and everything else - the actual security, link generation, index filtering - just happens. Hammock inspects your routes and resources to generate a routing tree for each resource. Parent resources in a nested route are handled transparently at every point - record retrieval, creation, and linking. It makes more sense when you see how it works though, so check out the screencast!}
11
+ s.email = ["ben@hoskings.net"]
12
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.rdoc", "misc/scaffold.txt"]
13
+ s.files = ["History.txt", "LICENSE", "Manifest.txt", "README.rdoc", "Rakefile", "hammock.gemspec", "lib/hammock.rb", "lib/hammock/ajaxinate.rb", "lib/hammock/callback.rb", "lib/hammock/callbacks.rb", "lib/hammock/canned_scopes.rb", "lib/hammock/constants.rb", "lib/hammock/controller_attributes.rb", "lib/hammock/export_scope.rb", "lib/hammock/hamlink_to.rb", "lib/hammock/javascript_buffer.rb", "lib/hammock/logging.rb", "lib/hammock/model_attributes.rb", "lib/hammock/model_logging.rb", "lib/hammock/monkey_patches/action_pack.rb", "lib/hammock/monkey_patches/active_record.rb", "lib/hammock/monkey_patches/array.rb", "lib/hammock/monkey_patches/hash.rb", "lib/hammock/monkey_patches/logger.rb", "lib/hammock/monkey_patches/module.rb", "lib/hammock/monkey_patches/numeric.rb", "lib/hammock/monkey_patches/object.rb", "lib/hammock/monkey_patches/route_set.rb", "lib/hammock/monkey_patches/string.rb", "lib/hammock/overrides.rb", "lib/hammock/resource_mapping_hooks.rb", "lib/hammock/resource_retrieval.rb", "lib/hammock/restful_actions.rb", "lib/hammock/restful_rendering.rb", "lib/hammock/restful_support.rb", "lib/hammock/route_drawing_hooks.rb", "lib/hammock/route_for.rb", "lib/hammock/route_node.rb", "lib/hammock/route_step.rb", "lib/hammock/scope.rb", "lib/hammock/suggest.rb", "lib/hammock/utils.rb", "misc/scaffold.txt", "misc/template.rb", "tasks/hammock_tasks.rake", "test/hammock_test.rb"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/benhoskings/hammock}
16
+ s.rdoc_options = ["--main", "README.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{hammock}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{Hammock is a Rails plugin that eliminates redundant code in a very RESTful manner}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<rails>, ["~> 2.2"])
28
+ s.add_runtime_dependency(%q<benhoskings-ambitious-activerecord>, ["~> 0.1.3.5"])
29
+ s.add_development_dependency(%q<newgem>, [">= 1.3.0"])
30
+ s.add_development_dependency(%q<hoe>, [">= 1.8.0"])
31
+ else
32
+ s.add_dependency(%q<rails>, ["~> 2.2"])
33
+ s.add_dependency(%q<benhoskings-ambitious-activerecord>, ["~> 0.1.3.5"])
34
+ s.add_dependency(%q<newgem>, [">= 1.3.0"])
35
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
36
+ end
37
+ else
38
+ s.add_dependency(%q<rails>, ["~> 2.2"])
39
+ s.add_dependency(%q<benhoskings-ambitious-activerecord>, ["~> 0.1.3.5"])
40
+ s.add_dependency(%q<newgem>, [">= 1.3.0"])
41
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
42
+ end
43
+ end
@@ -0,0 +1,154 @@
1
+ module Hammock
2
+ module Ajaxinate
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
+ def ajax_button verb, record, opts = {}
16
+ ajax_link verb, record, opts.merge(:class => [opts[:class], 'button'].squash.join(' '))
17
+ end
18
+
19
+ def ajax_link verb, record, opts = {}
20
+ if can_verb_entity?(verb, record)
21
+ route = ajaxinate verb, record, opts
22
+
23
+ content_tag :a,
24
+ opts[:text] || route.verb.to_s.capitalize,
25
+ :class => [opts[:class], link_class_for(route.verb, record)].squash.join(' '),
26
+ :href => route.path,
27
+ :onclick => 'return false;',
28
+ :style => opts[:style]
29
+ end
30
+ end
31
+
32
+ def ajaxinate verb, record, opts = {}
33
+ record_attributes = {record.base_model => record.unsaved_attributes}
34
+ link_params = {record.base_model => (opts.delete(:record) || {}) }.merge(opts[:params] || {})
35
+ route = route_for verb, record
36
+ attribute = link_params[:attribute]
37
+ link_class = link_class_for route.verb, record, attribute
38
+
39
+ link_params[:_method] = route.http_method
40
+ link_params[:format] = opts[:format].to_s
41
+
42
+ form_elements_hash = if route.get?
43
+ '{ }'
44
+ elsif attribute.blank?
45
+ "jQuery('form').serializeHash()"
46
+ else
47
+ "{ '#{record.base_model}[#{attribute}]': $('.#{link_class}').val() }"
48
+ end
49
+
50
+ response_action = case link_params[:format].to_s
51
+ when 'js'
52
+ "eval(response)"
53
+ else
54
+ "jQuery('.#{opts[:target] || link_class + '_target'}').before(response).remove()"
55
+ end
56
+
57
+ # TODO check the response code in the callback, and replace :after with :success and :failure.
58
+ js = %Q{
59
+ jQuery('.#{link_class}').#{opts[:on] || 'click'}(function() {
60
+ /*if (#{attribute.blank? ? 'false' : 'true'} && (jQuery('.#{link_class}_target .original_value').html() == jQuery('.#{link_class}_target .modify input').val())) {
61
+ eval("#{clean_snippet opts[:skipped]}");
62
+ } else*/ if (false == eval("#{clean_snippet opts[:before]}")) {
63
+ // before callback failed
64
+ } else { // fire the request
65
+ jQuery.#{route.fake_http_method}(
66
+ '#{route.path}',
67
+ jQuery.extend(
68
+ #{record_attributes.to_flattened_json},
69
+ #{form_elements_hash},
70
+ #{link_params.to_flattened_json},
71
+ #{forgery_key_json(route.http_method)}
72
+ ),
73
+ function(response) {
74
+ #{response_action};
75
+ eval("#{clean_snippet opts[:after]}");
76
+ }
77
+ );
78
+ }
79
+ });
80
+ }
81
+
82
+ append_javascript js
83
+ route
84
+ end
85
+
86
+ def status_callback
87
+ %Q{
88
+ if ('success' == textStatus) {
89
+ jQuery('.success', jQuery('#' + jQuery(data).attr("id"))).show().fadeOut(4000);
90
+ } else {
91
+ jQuery('.statuses .failure', obj).hide();
92
+ jQuery('.statuses .failure', obj).show().parents('obj').BlindUp();
93
+ }
94
+ }
95
+ end
96
+
97
+ def jquery_xhr verb, record, opts = {}
98
+ route = route_for verb, record
99
+ params = if opts[:params].is_a?(String)
100
+ opts[:params].chomp(',').start_with('{').end_with('}')
101
+ else
102
+ (opts[:params] || {}).merge(record.base_model => (opts[:record] || {})).to_flattened_json
103
+ end
104
+
105
+ response_action = case opts[:format].to_s
106
+ when 'js'
107
+ "eval(data);"
108
+ else
109
+ "obj.replaceWith(data);"
110
+ end
111
+
112
+ %Q{
113
+ if (typeof(obj) != 'undefined') {
114
+ jQuery('.spinner', obj).show();
115
+ }
116
+
117
+ jQuery.#{route.fake_http_method}(
118
+ '#{route.path}',
119
+ jQuery.extend(
120
+ #{params},
121
+ {format: '#{opts[:format] || 'html'}', _method: '#{route.http_method}'},
122
+ #{forgery_key_json(route.http_method)}
123
+ ),
124
+ function(data, textStatus) {
125
+ #{response_action}
126
+ #{status_callback}
127
+ #{(opts[:callback] || '').end_with(';')}
128
+ }
129
+ );
130
+ }
131
+ end
132
+
133
+ private
134
+
135
+ def link_class_for verb, record, attribute = nil
136
+ [verb, record.description, attribute].compact.join('_')
137
+ end
138
+
139
+ def clean_snippet snippet
140
+ report "Double quote detected in snippet '#{snippet}'" if snippet['"'] unless snippet.nil?
141
+ (snippet || '').gsub("\n", '\n').end_with(';')
142
+ end
143
+
144
+ def forgery_key_json request_method = nil
145
+ if !protect_against_forgery? || (:get == (request_method || request.method))
146
+ '{ }'
147
+ else
148
+ "{ '#{request_forgery_protection_token}': encodeURIComponent('#{escape_javascript(form_authenticity_token)}') }"
149
+ end
150
+ end
151
+
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,16 @@
1
+ module Hammock
2
+
3
+ class Callback < ActiveSupport::Callbacks::Callback
4
+ private
5
+
6
+ def evaluate_method method, *args, &block
7
+ if method.is_a? Proc
8
+ # puts "was a Hammock::Callback proc within #{args.first.class}."
9
+ method.bind(args.shift).call(*args, &block)
10
+ else
11
+ super
12
+ end
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,94 @@
1
+ module Hammock
2
+ module Callbacks
3
+ LoadFirst = true
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+
9
+ base.class_eval {
10
+ include ActiveSupport::Callbacks
11
+
12
+ define_hammock_callbacks *%w[
13
+ before_find during_find after_failed_find
14
+
15
+ before_index before_show
16
+ before_modify before_new before_edit
17
+
18
+ before_save after_save after_failed_save
19
+ before_create after_create after_failed_create
20
+ before_update after_update after_failed_update
21
+ before_destroy after_destroy
22
+ before_undestroy after_undestroy
23
+
24
+ before_suggest after_suggest
25
+ ]
26
+ }
27
+ end
28
+
29
+ module ClassMethods
30
+
31
+ def define_hammock_callbacks *callbacks
32
+ callbacks.each do |callback|
33
+ class_eval <<-"end_eval"
34
+ def self.#{callback}(*methods, &block)
35
+ callbacks = if !block_given? || methods.length > 1
36
+ CallbackChain.build(:#{callback}, *methods, &block)
37
+ else # hammock-style callback
38
+ if methods.empty?
39
+ log "<-- you should give this callback a description", :skip => 1
40
+ elsif !methods.first.is_a?(String)
41
+ raise ArgumentError, "Inline callback definitions require a description as their sole argument."
42
+ else
43
+ # logger.info "defining \#{methods.first} on \#{name} with method \#{block.inspect}."
44
+ [Hammock::Callback.new(:#{callback}, block, :identifier => methods.first)]
45
+ end || []
46
+ end
47
+ # log callbacks
48
+ (@#{callback}_callbacks ||= CallbackChain.new).concat callbacks
49
+ end
50
+
51
+ def self.#{callback}_callback_chain
52
+ @#{callback}_callbacks ||= CallbackChain.new
53
+
54
+ if superclass.respond_to?(:#{callback}_callback_chain)
55
+ CallbackChain.new(superclass.#{callback}_callback_chain + @#{callback}_callbacks)
56
+ else
57
+ @#{callback}_callbacks
58
+ end
59
+ end
60
+ end_eval
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ module InstanceMethods
67
+ private
68
+
69
+ CallbackFail = false
70
+
71
+ def callback kind, *args
72
+ callback_chain_for(kind).all? {|cb|
73
+ # dlog "Calling #{kind} callback #{cb.method}"
74
+ result = cb.call(self, *args) != CallbackFail
75
+ log "#{self.class}.#{cb.kind} callback '#{cb.method}' failed." unless result
76
+ result
77
+ }
78
+ end
79
+
80
+ def required_callback kind, *args
81
+ callback(kind, *args) if has_callbacks_for?(kind)
82
+ end
83
+
84
+ def has_callbacks_for? kind
85
+ !callback_chain_for(kind).empty?
86
+ end
87
+
88
+ def callback_chain_for kind
89
+ self.class.send "#{kind}_callback_chain"
90
+ end
91
+
92
+ end
93
+ end
94
+ end