hammock 0.2.11.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +67 -0
- data/LICENSE +24 -0
- data/Manifest.txt +46 -0
- data/README.rdoc +105 -0
- data/Rakefile +29 -0
- data/hammock.gemspec +43 -0
- data/lib/hammock/ajaxinate.rb +154 -0
- data/lib/hammock/callback.rb +16 -0
- data/lib/hammock/callbacks.rb +94 -0
- data/lib/hammock/canned_scopes.rb +125 -0
- data/lib/hammock/constants.rb +9 -0
- data/lib/hammock/controller_attributes.rb +52 -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 +53 -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 +231 -0
- data/lib/hammock/monkey_patches/array.rb +73 -0
- data/lib/hammock/monkey_patches/hash.rb +57 -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 +28 -0
- data/lib/hammock/monkey_patches/string.rb +197 -0
- data/lib/hammock/overrides.rb +36 -0
- data/lib/hammock/resource_mapping_hooks.rb +28 -0
- data/lib/hammock/resource_retrieval.rb +119 -0
- data/lib/hammock/restful_actions.rb +172 -0
- data/lib/hammock/restful_rendering.rb +114 -0
- data/lib/hammock/restful_support.rb +186 -0
- data/lib/hammock/route_drawing_hooks.rb +22 -0
- data/lib/hammock/route_for.rb +59 -0
- data/lib/hammock/route_node.rb +159 -0
- data/lib/hammock/route_step.rb +87 -0
- data/lib/hammock/scope.rb +127 -0
- data/lib/hammock/suggest.rb +36 -0
- data/lib/hammock/utils.rb +42 -0
- data/lib/hammock.rb +29 -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 +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
|