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.
- 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
@@ -0,0 +1,186 @@
|
|
1
|
+
module Hammock
|
2
|
+
module RestfulSupport
|
3
|
+
def self.included base # :nodoc:
|
4
|
+
base.send :include, InstanceMethods
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
|
7
|
+
base.class_eval {
|
8
|
+
before_modify :set_editing
|
9
|
+
# TODO Investigate the usefulness of this.
|
10
|
+
# before_destroy :set_editing
|
11
|
+
before_create :set_creator_id_if_appropriate
|
12
|
+
before_create :set_new_or_deleted_before_save
|
13
|
+
before_undestroy :set_new_or_deleted_before_save
|
14
|
+
helper_method :mdl, :mdl_name, :editing?, :nested_within?, :partial_exists?
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
# The model this controller operates on. Defined as the singularized controller name. For example, for +GelatinousBlobsController+, this will return the +GelatinousBlob+ class.
|
21
|
+
def mdl
|
22
|
+
@hammock_cached_mdl ||= Object.const_get to_s.sub('Controller', '').classify
|
23
|
+
end
|
24
|
+
# The lowercase name of the model this controller operates on. For example, for +GelatinousBlobsController+, this will return "gelatinous_blob".
|
25
|
+
def mdl_name
|
26
|
+
@hammock_cached_mdl_name ||= to_s.sub('Controller', '').singularize.underscore
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
private
|
33
|
+
|
34
|
+
# The model this controller operates on. Defined as the singularized controller name. For example, for +GelatinousBlobsController+, this will return the +GelatinousBlob+ class.
|
35
|
+
def mdl
|
36
|
+
self.class.mdl
|
37
|
+
end
|
38
|
+
# The lowercase name of the model this controller operates on. For example, for +GelatinousBlobsController+, this will return "gelatinous_blob".
|
39
|
+
def mdl_name
|
40
|
+
self.class.mdl_name
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the node in the Hammock routing map corresponding to the (possibly nested) resource handling the current request.
|
44
|
+
def current_hammock_resource
|
45
|
+
nesting_resources = params.keys.select {|k| /_id$/ =~ k }.map {|k| k.gsub(/_id$/, '').pluralize }
|
46
|
+
ActionController::Routing::Routes.route_map.base_for nesting_resources.push(mdl.resource_name).map(&:to_sym)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns true if the current action represents an edit on +record+.
|
50
|
+
#
|
51
|
+
# For example, consider the route <tt>/articles/3/comments/31/edit</tt>, which fires <tt>CommentsController#edit</tt>. The nested route handler would assign <tt>@comment</tt> and <tt>@article</tt> to the appropriate records, and then the following would be observed:
|
52
|
+
# editing?(@comment) #=> true
|
53
|
+
# editing?(@article) #=> false
|
54
|
+
def editing? record
|
55
|
+
record == @editing
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns <tt>params[key]</tt>, defaulting to an empty Hash if <tt>params[key]</tt> can't receive :[].
|
59
|
+
#
|
60
|
+
# This is useful for concise nested parameter access. For example, if <tt>params[:account]</tt> is nil:
|
61
|
+
# params[:account][:email] #=> NoMethodError: undefined method `[]' for nil:NilClass
|
62
|
+
# params_for(:account)[:email] #=> nil
|
63
|
+
def params_for key
|
64
|
+
params[key] || {}
|
65
|
+
end
|
66
|
+
|
67
|
+
def assign_entity record_or_records
|
68
|
+
@entity = if record_or_records.nil?
|
69
|
+
# Fail
|
70
|
+
elsif record_or_records.is_a? ActiveRecord::Base
|
71
|
+
instance_variable_set "@#{mdl_name}", (@record = record_or_records)
|
72
|
+
elsif record_or_records.is_a? Ambition::Context
|
73
|
+
log "Unkicked query: #{record_or_records.to_hash.inspect}"
|
74
|
+
instance_variable_set "@#{mdl_name.pluralize}", (@records = record_or_records)
|
75
|
+
elsif record_or_records.is_a? Array
|
76
|
+
instance_variable_set "@#{mdl_name.pluralize}", (@records = record_or_records)
|
77
|
+
else
|
78
|
+
raise "Unknown record(s) type #{record_or_records.class}."
|
79
|
+
end
|
80
|
+
|
81
|
+
@entity if assign_nesting_entities
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def assign_nesting_entities
|
87
|
+
results = nesting_scope_list.map &:kick
|
88
|
+
|
89
|
+
if results.all? {|records| records.length == 1 }
|
90
|
+
results.map {|records|
|
91
|
+
records.first
|
92
|
+
}.all? {|record|
|
93
|
+
log "nested record #{record.concise_inspect} assigned to @#{record.base_model}."
|
94
|
+
add_nested_entity record
|
95
|
+
instance_variable_set "@#{record.base_model}", record
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def make_new_record resource = mdl
|
101
|
+
resource.new_with(params_for(resource.symbolize))
|
102
|
+
end
|
103
|
+
|
104
|
+
def assign_createable
|
105
|
+
assign_entity make_createable
|
106
|
+
end
|
107
|
+
|
108
|
+
def make_createable resource = mdl
|
109
|
+
if !(new_record = make_new_record(resource))
|
110
|
+
log "Couldn't create a new #{resource.base_model} with the given nesting level and parameters."
|
111
|
+
elsif !new_record.createable_by?(current_user)
|
112
|
+
log "#{requester_name} can't create #{new_record.resource_name}."
|
113
|
+
else
|
114
|
+
new_record
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def current_nested_records
|
119
|
+
(@current_nested_records || []).dup
|
120
|
+
end
|
121
|
+
|
122
|
+
def current_nested_resources
|
123
|
+
(@current_nested_resources || []).dup
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_nested_entity entity
|
127
|
+
(@current_nested_records ||= []).push entity
|
128
|
+
(@current_nested_resources ||= []).push entity.resource
|
129
|
+
end
|
130
|
+
|
131
|
+
def nested_within? record_or_resource
|
132
|
+
if record_or_resource.is_a? ActiveRecord::Base
|
133
|
+
current_nested_records.include? record_or_resource
|
134
|
+
else
|
135
|
+
current_nested_resources.include? record_or_resource
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def safe_verb_and_implication?
|
140
|
+
request.get? && !action_name.to_s.in?(Hammock::Constants::ImpliedUnsafeActions)
|
141
|
+
end
|
142
|
+
|
143
|
+
def set_editing
|
144
|
+
@editing = @record
|
145
|
+
end
|
146
|
+
|
147
|
+
def set_new_or_deleted_before_save
|
148
|
+
@record.set_new_or_deleted_before_save
|
149
|
+
end
|
150
|
+
|
151
|
+
# TODO process /^creating_\w+_id$/ as well
|
152
|
+
def set_creator_id_if_appropriate
|
153
|
+
if @record.respond_to? :creator_id=
|
154
|
+
if current_user.nil?
|
155
|
+
log "Warning: @#{@record.base_model}.creator_id isn't being set, since current_user was nil."
|
156
|
+
else
|
157
|
+
@record.creator_id = current_user.id
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def partial_exists? name, format = nil
|
163
|
+
partial_name, ctrler_name = name.split('/', 2).reverse
|
164
|
+
!Dir.glob(File.join(
|
165
|
+
RAILS_ROOT,
|
166
|
+
'app/views',
|
167
|
+
ctrler_name || '',
|
168
|
+
"_#{partial_name}.#{format || request.format.to_sym.to_s}.*"
|
169
|
+
)).empty?
|
170
|
+
end
|
171
|
+
|
172
|
+
def redirect_back_or opts = {}, *parameters_for_method_reference
|
173
|
+
if request.referer.blank?
|
174
|
+
redirect_to opts, *parameters_for_method_reference
|
175
|
+
else
|
176
|
+
redirect_to request.referer
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def rendered_or_redirected?
|
181
|
+
@performed_render || @performed_redirect
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Hammock
|
2
|
+
module RouteDrawingHooks
|
3
|
+
MixInto = ActionController::Routing::RouteSet
|
4
|
+
|
5
|
+
def self.included base # :nodoc:
|
6
|
+
base.send :include, Methods
|
7
|
+
|
8
|
+
base.class_eval {
|
9
|
+
alias_method_chain_once :draw, :hammock_route_map_init
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
module Methods
|
14
|
+
|
15
|
+
def draw_with_hammock_route_map_init &block
|
16
|
+
ActionController::Routing::Routes.send :initialize_hammock_route_map
|
17
|
+
draw_without_hammock_route_map_init &block
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Hammock
|
2
|
+
module RouteFor
|
3
|
+
def self.included base # :nodoc:
|
4
|
+
base.send :include, InstanceMethods
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
|
7
|
+
base.class_eval {
|
8
|
+
helper_method :path_for, :nested_path_for, :route_for, :nested_route_for
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
private
|
17
|
+
|
18
|
+
def path_for *args
|
19
|
+
route_for(*args).path
|
20
|
+
end
|
21
|
+
|
22
|
+
def nested_path_for *args
|
23
|
+
nested_route_for(*args).path
|
24
|
+
end
|
25
|
+
|
26
|
+
def route_for *args
|
27
|
+
args.compact!
|
28
|
+
opts = args.extract_options!
|
29
|
+
verb = args.shift if args.first.is_a?(Symbol)
|
30
|
+
|
31
|
+
ActionController::Routing::Routes.route_map.for verb_for(verb, args.last), args, opts
|
32
|
+
end
|
33
|
+
|
34
|
+
def verb_for requested_verb, record
|
35
|
+
requested_verb = :show if requested_verb.blank?
|
36
|
+
|
37
|
+
if (:show == requested_verb) && record.is_a?(Class)
|
38
|
+
:index
|
39
|
+
elsif :modify == requested_verb
|
40
|
+
record.new_record? ? :new : :edit
|
41
|
+
elsif :save == requested_verb
|
42
|
+
record.new_record? ? :create : :update
|
43
|
+
else
|
44
|
+
requested_verb
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def nested_route_for *resources
|
49
|
+
resources.delete_if &:nil?
|
50
|
+
requested_verb = resources.shift if resources.first.is_a?(Symbol)
|
51
|
+
args = current_nested_records.concat(resources)
|
52
|
+
|
53
|
+
args.unshift(requested_verb) unless requested_verb.nil?
|
54
|
+
route_for *args
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Hammock
|
2
|
+
class RouteNode < ActionController::Resources::Resource
|
3
|
+
|
4
|
+
DefaultRecordVerbs = {
|
5
|
+
:show => :get,
|
6
|
+
:edit => :get,
|
7
|
+
:update => :put,
|
8
|
+
:destroy => :delete
|
9
|
+
}.freeze
|
10
|
+
DefaultResourceVerbs = {
|
11
|
+
:index => :get
|
12
|
+
}.freeze
|
13
|
+
DefaultBuildVerbs = {
|
14
|
+
:new => :get,
|
15
|
+
:create => :post
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
attr_reader :mdl, :resource, :parent, :children, :routing_parent, :record_routes, :resource_routes, :build_routes
|
19
|
+
|
20
|
+
def initialize entity = nil, options = {}
|
21
|
+
@parent = options[:parent]
|
22
|
+
@children = {}
|
23
|
+
unless root?
|
24
|
+
@mdl = entity if entity.is_a?(Symbol)
|
25
|
+
@routing_parent = determine_routing_parent
|
26
|
+
define_routes options
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def resource
|
31
|
+
# TODO performance
|
32
|
+
Object.const_get mdl.to_s.classify rescue nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def root?
|
36
|
+
parent.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def ancestry
|
40
|
+
root? ? [] : parent.ancestry.push(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def for verb, entities, options
|
44
|
+
raise "Hammock::RouteNode#for requires an explicitly specified verb as its first argument." unless verb.is_a?(Symbol)
|
45
|
+
raise "Hammock::RouteNode#for requires an Array of at least one record or resource." if entities.empty? || !entities.is_a?(Array)
|
46
|
+
|
47
|
+
entity = entities.shift
|
48
|
+
|
49
|
+
if entities.empty?
|
50
|
+
steps_for verb, entity
|
51
|
+
else
|
52
|
+
children[entity.resource_sym].for(verb, entities, options).within steps_for(nil, entity)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def base_for resources
|
57
|
+
# puts "base_for<#{mdl}>: resources=#{resources.inspect}."
|
58
|
+
if resources.empty?
|
59
|
+
self
|
60
|
+
else
|
61
|
+
match = nil
|
62
|
+
children.values.detect {|child|
|
63
|
+
# puts " Trying #{child.ancestry.map(&:mdl).inspect} for #{resources.inspect}"
|
64
|
+
if !resources.include?(child.mdl)
|
65
|
+
# puts " Can't match #{resources.inspect} against #{child.mdl}."
|
66
|
+
nil
|
67
|
+
else
|
68
|
+
# puts " Matched #{child.mdl} from #{resources.inspect}."
|
69
|
+
match = child.base_for resources.discard(child.mdl)
|
70
|
+
end
|
71
|
+
} #|| raise("There is no routing path for #{resources.map(&:inspect).inspect}.")
|
72
|
+
match
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def nesting_scope_list_for params
|
77
|
+
if root?
|
78
|
+
[ ]
|
79
|
+
else
|
80
|
+
parent.nesting_scope_list_for(params).push nesting_scope_segment_for(params)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def nesting_scope_segment_for params
|
85
|
+
raise "The root of the route map isn't associated with a resource." if root?
|
86
|
+
puts "is this undefined? #{resource.accessible_attributes_on_create.inspect}"
|
87
|
+
value = params.delete resource.param_key
|
88
|
+
puts "resource.select {|r| r.#{resource.routing_attribute} == value }"
|
89
|
+
eval "resource.select {|r| r.#{resource.routing_attribute} == value }"
|
90
|
+
end
|
91
|
+
|
92
|
+
def add entity, options, steps = nil
|
93
|
+
if steps.nil?
|
94
|
+
add entity, options, (options[:name_prefix] || '').chomp('_').split('_').map {|i| i.pluralize.underscore.to_sym }
|
95
|
+
elsif steps.empty?
|
96
|
+
add_child entity, options
|
97
|
+
else
|
98
|
+
children[steps.shift].add entity, options, steps
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def routeable_as verb, entity
|
103
|
+
if entity.record? && record_routes[verb || :show]
|
104
|
+
:record
|
105
|
+
elsif entity.resource? && resource_routes[verb || :index]
|
106
|
+
:resource
|
107
|
+
elsif !verb.nil? && build_routes[verb]
|
108
|
+
:build
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def define_routes options
|
115
|
+
@record_routes = DefaultRecordVerbs.dup.update(options[:member] || {})
|
116
|
+
@resource_routes = DefaultResourceVerbs.dup.update(options[:collection] || {})
|
117
|
+
@build_routes = DefaultBuildVerbs.dup.update(options[:build] || {})
|
118
|
+
end
|
119
|
+
|
120
|
+
def determine_routing_parent
|
121
|
+
puts "\ndetermine_routing_parent: #{mdl}:"
|
122
|
+
if parent.nil? || parent.resource.nil?
|
123
|
+
puts "Resource for #{mdl} has either no parent or no resource - not nestable."
|
124
|
+
nil
|
125
|
+
else
|
126
|
+
puts "reflections: #{resource.reflections.keys.inspect}"
|
127
|
+
puts "nestable_routing_resources: #{resource.nestable_routing_resources.inspect}"
|
128
|
+
scannable_reflections = resource.nestable_routing_resources.nil? ? resource.reflections : resource.reflections.dragnet(*resource.nestable_routing_resources)
|
129
|
+
puts "scannable reflections: #{scannable_reflections.keys.inspect}"
|
130
|
+
valid_reflections = scannable_reflections.selekt {|k,v| puts "#{v.klass}<#{v.object_id}> == #{parent.resource}<#{parent.resource.object_id}> #=> #{v.klass == parent.resource}"; v.klass == parent.resource }
|
131
|
+
puts "valid reflections: #{valid_reflections.keys.inspect}"
|
132
|
+
|
133
|
+
if valid_reflections.keys.length < 1
|
134
|
+
raise "The routing table specifies that #{mdl} is nested within #{parent.mdl}, but there is no ActiveRecord association linking #{resource} to #{parent.resource}. Example: 'belongs_to :#{parent.resource.base_model}' in the #{resource} model."
|
135
|
+
elsif valid_reflections.keys.length > 1
|
136
|
+
raise "#{resource} defines more than one association to #{parent.resource} (#{valid_reflections.keys.map(&:to_s).join(', ')}). That's fine, but you need to use #{resource}.nest_within to specify the one Hammock should nest scopes through. For example, 'nest_within #{valid_reflections.keys.first.inspect}' in the #{resource} model."
|
137
|
+
end
|
138
|
+
|
139
|
+
valid_reflections.keys.first
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def add_child entity, options
|
144
|
+
child = RouteNode.new entity, options.merge(:parent => self)
|
145
|
+
children[child.mdl] = child
|
146
|
+
end
|
147
|
+
|
148
|
+
def steps_for verb, entity
|
149
|
+
child = children[entity.resource_sym]
|
150
|
+
|
151
|
+
if child.nil?
|
152
|
+
raise "No routes are defined for #{entity.resource}#{' within ' + ancestry.map {|r| r.mdl.to_s }.join(', ') unless ancestry.empty?}."
|
153
|
+
else
|
154
|
+
RouteStep.new(child).for(verb, entity)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Hammock
|
2
|
+
class RouteStep
|
3
|
+
attr_reader :resource, :routeable_as, :verb, :entity, :parent
|
4
|
+
|
5
|
+
def initialize resource
|
6
|
+
@resource = resource
|
7
|
+
end
|
8
|
+
|
9
|
+
def for verb, entity
|
10
|
+
routeable_as = resource.routeable_as(verb, entity)
|
11
|
+
|
12
|
+
if !routeable_as
|
13
|
+
raise "The verb '#{verb}' can't be applied to " + (entity.record? ? "#{entity.resource} records" : "the #{entity.resource} resource") + "."
|
14
|
+
elsif (:record == routeable_as) && entity.new_record?
|
15
|
+
raise "The verb '#{verb}' requires a #{entity.resource} with an ID (i.e. not a new record)."
|
16
|
+
elsif (:build == routeable_as) && entity.record? && !entity.new_record?
|
17
|
+
raise "The verb '#{verb}' requires either the #{entity.resource} resource, or a #{entity.resource} without an ID (i.e. a new record)."
|
18
|
+
else
|
19
|
+
@verb, @entity, @routeable_as = verb, entity, routeable_as
|
20
|
+
end
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def within parent
|
26
|
+
@parent = parent
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def setup?
|
31
|
+
!@entity.nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
def path params = nil
|
35
|
+
raise_unless_setup_while_trying_to 'render a path'
|
36
|
+
|
37
|
+
buf = '/'
|
38
|
+
buf << entity.resource_name
|
39
|
+
buf << '/' + entity.to_param if entity.record? && !entity.new_record?
|
40
|
+
buf << '/' + verb.to_s unless verb.nil? or implied_verb?(verb)
|
41
|
+
|
42
|
+
buf = parent.path + buf unless parent.nil?
|
43
|
+
buf << param_str(params)
|
44
|
+
|
45
|
+
buf
|
46
|
+
end
|
47
|
+
|
48
|
+
def http_method
|
49
|
+
raise_unless_setup_while_trying_to 'extract the HTTP method'
|
50
|
+
resource.send("#{routeable_as}_routes")[verb]
|
51
|
+
end
|
52
|
+
|
53
|
+
def fake_http_method
|
54
|
+
http_method.in?(:get, :post) ? http_method : :post
|
55
|
+
end
|
56
|
+
|
57
|
+
def get?; :get == http_method end
|
58
|
+
def post?; :post == http_method end
|
59
|
+
def put?; :put == http_method end
|
60
|
+
def delete?; :delete == http_method end
|
61
|
+
|
62
|
+
def safe?
|
63
|
+
get? && !verb.in?(Hammock::Constants::ImpliedUnsafeActions)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def implied_verb? verb
|
69
|
+
verb.in? :index, :create, :show, :update, :destroy
|
70
|
+
end
|
71
|
+
|
72
|
+
def raise_unless_setup_while_trying_to task
|
73
|
+
raise "You have to call for(verb, entity) (and optionally within(parent)) on this RouteStep before you can #{task}." unless setup?
|
74
|
+
end
|
75
|
+
|
76
|
+
def param_str params
|
77
|
+
link_params = entity.record? ? entity.unsaved_attributes.merge(params || {}) : params
|
78
|
+
|
79
|
+
if link_params.blank?
|
80
|
+
''
|
81
|
+
else
|
82
|
+
'?' + {entity.base_model => link_params}.to_query
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Hammock
|
2
|
+
module Scope
|
3
|
+
def self.included base # :nodoc:
|
4
|
+
base.send :include, InstanceMethods
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
|
7
|
+
base.class_eval {
|
8
|
+
helper_method :can_verb_entity?
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
private
|
17
|
+
|
18
|
+
def can_verb_entity? verb, entity
|
19
|
+
if entity.is_a? ActiveRecord::Base
|
20
|
+
can_verb_record? verb, entity
|
21
|
+
else
|
22
|
+
can_verb_resource? verb, entity
|
23
|
+
end == :ok
|
24
|
+
end
|
25
|
+
|
26
|
+
def can_verb_resource? verb, resource
|
27
|
+
raise "The verb at #{call_point} must be supplied as a Symbol." unless verb.nil? || verb.is_a?(Symbol)
|
28
|
+
route = route_for verb, resource
|
29
|
+
if route.safe? && !resource.indexable_by(current_user)
|
30
|
+
log "#{requester_name} can't index #{resource.name.pluralize}. #{describe_call_point 4}"
|
31
|
+
:not_found
|
32
|
+
elsif !route.safe? && !make_createable(resource)
|
33
|
+
log "#{requester_name} can't #{verb} #{resource.name.pluralize}. #{describe_call_point 4}"
|
34
|
+
:read_only
|
35
|
+
else
|
36
|
+
# log "#{requester_name} can #{verb} #{resource.name.pluralize}."
|
37
|
+
:ok
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def can_verb_record? verb, record
|
42
|
+
raise "The verb at #{call_point} must be supplied as a Symbol." unless verb.nil? || verb.is_a?(Symbol)
|
43
|
+
route = route_for verb, record
|
44
|
+
if route.verb.in?(:save, :create) && record.new_record?
|
45
|
+
if !record.createable_by?(current_user)
|
46
|
+
log "#{requester_name} can't create a #{record.class} with #{record.attributes.inspect}. #{describe_call_point 4}"
|
47
|
+
:unauthed
|
48
|
+
else
|
49
|
+
:ok
|
50
|
+
end
|
51
|
+
else
|
52
|
+
if !record.readable_by?(current_user)
|
53
|
+
log "#{requester_name} can't see #{record.class}<#{record.id}>. #{describe_call_point 4}"
|
54
|
+
:not_found
|
55
|
+
elsif !route.safe? && !record.writeable_by?(current_user)
|
56
|
+
log "#{requester_name} can't #{verb} #{record.class}<#{record.id}>. #{describe_call_point 4}"
|
57
|
+
:read_only
|
58
|
+
else
|
59
|
+
# log "#{requester_name} can #{verb} #{record.class}<#{record.id}>."
|
60
|
+
:ok
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_verb_scope
|
66
|
+
if current_user && (scope_name = account_verb_scope?)
|
67
|
+
# log "got an account_verb_scope #{scope_name}."
|
68
|
+
mdl.send scope_name, current_user
|
69
|
+
elsif !(scope_name = public_verb_scope?)
|
70
|
+
log "No #{current_user.nil? ? 'public' : 'account'} #{scope_name_for_action} scope available for #{mdl}.#{' May be available after login.' if account_verb_scope?}"
|
71
|
+
nil
|
72
|
+
else
|
73
|
+
# log "got a #{scope_name} public_verb_scope."
|
74
|
+
mdl.send scope_name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def nesting_scope_list
|
79
|
+
@hammock_cached_nesting_scope_list = current_hammock_resource.parent.nesting_scope_list_for params.selekt {|k,v| /_id$/ =~ k }
|
80
|
+
end
|
81
|
+
|
82
|
+
def current_nest_scope
|
83
|
+
nesting_scope_list.reverse.inject {|acc,scope| acc.within scope }
|
84
|
+
end
|
85
|
+
|
86
|
+
def current_scope
|
87
|
+
log "#{current_hammock_resource.mdl}, #{current_hammock_resource.ancestry.map(&:mdl).inspect}"
|
88
|
+
if (verb_scope = current_verb_scope).nil?
|
89
|
+
nil
|
90
|
+
elsif (resultant_scope = verb_scope.within(current_nest_scope, current_hammock_resource.routing_parent)).nil?
|
91
|
+
nil
|
92
|
+
else
|
93
|
+
# puts "nest: #{current_nest_scope.clauses.inspect}"
|
94
|
+
# puts "verb: #{current_verb_scope.clauses.inspect}"
|
95
|
+
puts "chained in (#{resultant_scope.owner}) current_scope: #{resultant_scope.clauses.inspect}"
|
96
|
+
resultant_scope = resultant_scope.chain(custom_scope) unless custom_scope.nil?
|
97
|
+
resultant_scope.sort_by &mdl.sorter
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def scope_name_for_action
|
105
|
+
if 'index' == action_name
|
106
|
+
'index'
|
107
|
+
elsif safe_verb_and_implication?
|
108
|
+
'read'
|
109
|
+
else
|
110
|
+
'write'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def requester_name
|
115
|
+
current_user.nil? ? 'Anonymous' : "#{current_user.class}<#{current_user.id}>"
|
116
|
+
end
|
117
|
+
|
118
|
+
def account_verb_scope?
|
119
|
+
mdl.has_account_scope? scope_name_for_action
|
120
|
+
end
|
121
|
+
def public_verb_scope?
|
122
|
+
mdl.has_public_scope? scope_name_for_action
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Hammock
|
2
|
+
module Suggest
|
3
|
+
MixInto = ActiveRecord::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
|
+
|
12
|
+
def suggest given_fields, queries, limit = 15
|
13
|
+
if (fields = given_fields & columns.map(&:name)).length != given_fields.length
|
14
|
+
log "Invalid columns #{(given_fields - fields).inspect}."
|
15
|
+
else
|
16
|
+
find(:all,
|
17
|
+
:limit => limit,
|
18
|
+
:order => fields.map {|f| "#{f} ASC" }.join(', '),
|
19
|
+
:conditions => [
|
20
|
+
fields.map {|f|
|
21
|
+
([ "LOWER(#{table_name}.#{f}) LIKE ?" ] * queries.length).join(' AND ')
|
22
|
+
}.map {|clause|
|
23
|
+
"(#{clause})"
|
24
|
+
}.join(' OR ')
|
25
|
+
].concat(queries.map{|q| "%#{q}%" } * fields.length)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|