benhoskings-hammock 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,32 @@
1
+ == 0.2.6 2009-03-05
2
+
3
+ Access current_nested_records in nested_route_for through the method instead of directly.
4
+ Replaced #assign_nestable_resources with #assign_nesting_entities, to separately kick and assign an ivar for each of the current rou
5
+ Added nesting_scope_list to encapsulate what @hammock_nesting_scopes was set to before.
6
+ Updated nested_within? to use methods instead of ivars, removed duping from current_nested_[record,resource]s, and added add_nested_en
7
+ Updated empty_scope for sqlite, to bring it in line with public_scope.
8
+ Updated current_scope to fail gracefully if current_verb_scope (renamed from verb_scope along with [current_]nest_scope) returns nil.
9
+ Replaced RouteNode#nesting_scope_for with nesting_scope_list_for, and changed nest_scope call appropriately. The scopes are returned s
10
+ Replaced AR::Base#id_or_description with #description, added .description, and use in link_class_for for more consistent class names.
11
+ Split Hammock::Callback (previously HammockCallback) out into its own file.
12
+ Split RouteNode and RouteStep (previously HammockResource and HammockRoutePiece) out into their own files.
13
+ Reject Hammock:: classes from hammock load. Classes are modules. Who knew?
14
+ Changed inheritable_attribute keys from strings to symbols.
15
+ Caching the model within HammockResource was causing stale class fails in development mode.
16
+ retrieve_record uses mdl.routing_attribute now instead of the old find_column_name.
17
+ Default routing_attribute to :id.
18
+ 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)
19
+ HammockRoutePiece erroneously had a root? check instead of a parent.nil? check - fixed.
20
+ Only set creator_id if someone is logged in.
21
+ Updated #nest_scope to use #nesting_scope_for instead of manually recursively selecting within a single context, and updated #curren
22
+ Re-enabled logging of Ambition queries as they are assigned as entities.
23
+ Replaced the questionable #current_route with #current_hammock_resource, which uses HammockResource#base_for.
24
+ Added HammockResource#base_for, #nesting_scope_for and #nesting_scope_segment_for.
25
+ Added HammockResource#determine_routing_parent, to determine which ActiveRecord reflection to use to generate joins for route queryi
26
+ Added Hash#selekt, to sidestep the ruby-1.8/1.9 #select changes.
27
+ Commented current_route-dependent code in hamlink_to until that is sorted out.
28
+ Test if obj is defined before attempting to select .spinner within it.
29
+
1
30
  == 0.2.4 2009-02-25
2
31
 
3
32
  * Initial gem release
@@ -6,6 +6,7 @@ Rakefile
6
6
  hammock.gemspec
7
7
  lib/hammock.rb
8
8
  lib/hammock/ajaxinate.rb
9
+ lib/hammock/callback.rb
9
10
  lib/hammock/callbacks.rb
10
11
  lib/hammock/canned_scopes.rb
11
12
  lib/hammock/constants.rb
@@ -34,6 +35,8 @@ lib/hammock/restful_rendering.rb
34
35
  lib/hammock/restful_support.rb
35
36
  lib/hammock/route_drawing_hooks.rb
36
37
  lib/hammock/route_for.rb
38
+ lib/hammock/route_node.rb
39
+ lib/hammock/route_step.rb
37
40
  lib/hammock/scope.rb
38
41
  lib/hammock/suggest.rb
39
42
  lib/hammock/utils.rb
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{hammock}
5
- s.version = "0.2.5"
5
+ s.version = "0.2.6"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Ben Hoskings"]
9
- s.date = %q{2009-02-27}
9
+ s.date = %q{2009-03-05}
10
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
11
  s.email = ["ben@hoskings.net"]
12
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/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/scope.rb", "lib/hammock/suggest.rb", "lib/hammock/utils.rb", "misc/scaffold.txt", "misc/template.rb", "tasks/hammock_tasks.rake", "test/hammock_test.rb", "test/rails_root/test/test_helper.rb"]
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
14
  s.has_rdoc = true
15
15
  s.homepage = %q{http://github.com/benhoskings/hammock}
16
16
  s.rdoc_options = ["--main", "README.rdoc"]
@@ -18,7 +18,6 @@ Gem::Specification.new do |s|
18
18
  s.rubyforge_project = %q{hammock}
19
19
  s.rubygems_version = %q{1.3.1}
20
20
  s.summary = %q{Hammock is a Rails plugin that eliminates redundant code in a very RESTful manner}
21
- s.test_files = ["test/rails_root/test/test_helper.rb"]
22
21
 
23
22
  if s.respond_to? :specification_version then
24
23
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -8,13 +8,13 @@ Dir.glob("#{File.dirname __FILE__}/hammock/**/*.rb").each {|dep|
8
8
  } if defined?(RAILS_ROOT) # Loading Hammock components under 'rake package' fails.
9
9
 
10
10
  module Hammock
11
- VERSION = '0.2.5'
11
+ VERSION = '0.2.6'
12
12
 
13
13
  def self.included base # :nodoc:
14
14
  Hammock.constants.map {|constant_name|
15
15
  Hammock.const_get constant_name
16
16
  }.select {|constant|
17
- constant.is_a? Module
17
+ constant.is_a?(Module) && !constant.is_a?(Class)
18
18
  }.partition {|mod|
19
19
  mod.constants.include?('LoadFirst') && mod::LoadFirst
20
20
  }.flatten.each {|mod|
@@ -133,7 +133,7 @@ module Hammock
133
133
  private
134
134
 
135
135
  def link_class_for verb, record, attribute = nil
136
- [verb, record.base_model, record.id, attribute].compact.join('_')
136
+ [verb, record.description, attribute].compact.join('_')
137
137
  end
138
138
 
139
139
  def clean_snippet snippet
@@ -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
@@ -28,19 +28,6 @@ module Hammock
28
28
 
29
29
  module ClassMethods
30
30
 
31
- class HammockCallback < ActiveSupport::Callbacks::Callback
32
- private
33
-
34
- def evaluate_method method, *args, &block
35
- if method.is_a? Proc
36
- # puts "was a HammockCallback proc within #{args.first.class}."
37
- method.bind(args.shift).call(*args, &block)
38
- else
39
- super
40
- end
41
- end
42
- end
43
-
44
31
  def define_hammock_callbacks *callbacks
45
32
  callbacks.each do |callback|
46
33
  class_eval <<-"end_eval"
@@ -54,7 +41,7 @@ module Hammock
54
41
  raise ArgumentError, "Inline callback definitions require a description as their sole argument."
55
42
  else
56
43
  # logger.info "defining \#{methods.first} on \#{name} with method \#{block.inspect}."
57
- [HammockCallback.new(:#{callback}, block, :identifier => methods.first)]
44
+ [Hammock::Callback.new(:#{callback}, block, :identifier => methods.first)]
58
45
  end || []
59
46
  end
60
47
  # log callbacks
@@ -109,7 +109,11 @@ module Hammock
109
109
  end
110
110
 
111
111
  def empty_scope
112
- lambda {|record| false }
112
+ if sqlite?
113
+ lambda {|record| 0 } # TODO check what this should be
114
+ else
115
+ lambda {|record| false }
116
+ end
113
117
  end
114
118
 
115
119
  end
@@ -1,4 +1,6 @@
1
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
+
2
4
  module Constants
3
5
 
4
6
  ImpliedUnsafeActions = [:new, :edit, :destroy]
@@ -31,15 +31,6 @@ module Hammock
31
31
  def find_on_create
32
32
  write_inheritable_attribute :find_on_create, true
33
33
  end
34
-
35
- # Use +find_column+ to specify the name of an alternate column with which record lookups should be performed.
36
- #
37
- # This is useful for controllers that are indexed by primary key, but are accessed with URLs containing some other unique attribute of the resource, like a randomly-generated key.
38
- # find_column :key
39
- def find_column column_name
40
- # TODO define to_param on model.
41
- write_inheritable_attribute :find_column, column_name
42
- end
43
34
  end
44
35
 
45
36
  module InstanceMethods
@@ -57,10 +48,6 @@ module Hammock
57
48
  def findable_on_create?
58
49
  self.class.read_inheritable_attribute :find_on_create
59
50
  end
60
-
61
- def find_column_name
62
- self.class.read_inheritable_attribute(:find_column) || :id
63
- end
64
51
  end
65
52
  end
66
53
  end
@@ -9,24 +9,39 @@ module Hammock
9
9
 
10
10
  module ClassMethods
11
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
12
21
  def has_defaults attrs
13
- write_inheritable_attribute "default_attributes", (default_attributes || {}).merge(attrs)
22
+ write_inheritable_attribute :default_attributes, (default_attributes || {}).merge(attrs)
14
23
  end
15
24
  def attr_accessible_on_create *attributes
16
- write_inheritable_attribute "attr_accessible_on_create", Set.new(attributes.map(&:to_s)) + (accessible_attributes_on_create || [])
25
+ write_inheritable_attribute :attr_accessible_on_create, Set.new(attributes.map(&:to_s)) + (accessible_attributes_on_create || [])
17
26
  end
18
27
  def attr_accessible_on_update *attributes
19
- write_inheritable_attribute "attr_accessible_on_update", Set.new(attributes.map(&:to_s)) + (accessible_attributes_on_update || [])
28
+ write_inheritable_attribute :attr_accessible_on_update, Set.new(attributes.map(&:to_s)) + (accessible_attributes_on_update || [])
20
29
  end
21
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
22
37
  def default_attributes
23
- read_inheritable_attribute("default_attributes") || {}
38
+ read_inheritable_attribute(:default_attributes) || {}
24
39
  end
25
40
  def accessible_attributes_on_create
26
- read_inheritable_attribute "attr_accessible_on_create"
41
+ read_inheritable_attribute :attr_accessible_on_create
27
42
  end
28
43
  def accessible_attributes_on_update
29
- read_inheritable_attribute "attr_accessible_on_update"
44
+ read_inheritable_attribute :attr_accessible_on_update
30
45
  end
31
46
 
32
47
  end
@@ -39,10 +39,18 @@ module Hammock
39
39
  base_class.to_s.pluralize.underscore
40
40
  end
41
41
 
42
+ def param_key
43
+ "#{base_model}_id"
44
+ end
45
+
42
46
  def base_model
43
47
  base_class.to_s.underscore
44
48
  end
45
49
 
50
+ def description
51
+ base_model
52
+ end
53
+
46
54
  def record?; false end
47
55
  def resource?; true end
48
56
 
@@ -122,12 +130,8 @@ module Hammock
122
130
  end
123
131
  end
124
132
 
125
- def id_or_description
126
- new_record? ? new_record_description : id
127
- end
128
-
129
- def new_record_description
130
- attributes.map {|k,v| "#{k}-#{(v.to_s || '')[0..10]}" }.join("_")
133
+ def description
134
+ new_record? ? "new_#{base_model}" : "#{base_model}_#{id}"
131
135
  end
132
136
 
133
137
  def base_model
@@ -21,6 +21,14 @@ module Hammock
21
21
  dup.discard! *keys
22
22
  end
23
23
 
24
+ def selekt &block
25
+ hsh = {}
26
+ each_pair {|k,v|
27
+ hsh[k] = v if yield(k,v)
28
+ }
29
+ hsh
30
+ end
31
+
24
32
  def dragnet *keys
25
33
  dup.dragnet! *keys
26
34
  end
@@ -20,179 +20,7 @@ module Hammock
20
20
  private
21
21
 
22
22
  def initialize_hammock_route_map
23
- self.route_map = HammockResource.new
24
- end
25
-
26
- class HammockResource < ActionController::Resources::Resource
27
- class HammockRoutePiece
28
- attr_reader :resource, :routeable_as, :verb, :entity, :parent
29
-
30
- def initialize resource
31
- @resource = resource
32
- end
33
-
34
- def for verb, entity
35
- routeable_as = resource.routeable_as(verb, entity)
36
-
37
- if !routeable_as
38
- raise "The verb '#{verb}' can't be applied to " + (entity.record? ? "#{entity.resource} records" : "the #{entity.resource} resource") + "."
39
- elsif (:record == routeable_as) && entity.new_record?
40
- raise "The verb '#{verb}' requires a #{entity.resource} with an ID (i.e. not a new record)."
41
- elsif (:build == routeable_as) && entity.record? && !entity.new_record?
42
- raise "The verb '#{verb}' requires either the #{entity.resource} resource, or a #{entity.resource} without an ID (i.e. a new record)."
43
- else
44
- @verb, @entity, @routeable_as = verb, entity, routeable_as
45
- end
46
-
47
- self
48
- end
49
-
50
- def within parent
51
- @parent = parent
52
- self
53
- end
54
-
55
- def setup?
56
- !@entity.nil?
57
- end
58
-
59
- def path params = nil
60
- raise_unless_setup_while_trying_to 'render a path'
61
-
62
- buf = '/'
63
- buf << entity.resource_name
64
- buf << '/' + entity.to_param if entity.record? && !entity.new_record?
65
- buf << '/' + verb.to_s unless verb.nil? or implied_verb?(verb)
66
-
67
- buf = parent.path + buf unless parent.nil?
68
- buf << param_str(params)
69
-
70
- buf
71
- end
72
-
73
- def http_method
74
- raise_unless_setup_while_trying_to 'extract the HTTP method'
75
- resource.send("#{routeable_as}_routes")[verb]
76
- end
77
-
78
- def fake_http_method
79
- http_method.in?(:get, :post) ? http_method : :post
80
- end
81
-
82
- def get?; :get == http_method end
83
- def post?; :post == http_method end
84
- def put?; :put == http_method end
85
- def delete?; :delete == http_method end
86
-
87
- def safe?
88
- get? && !verb.in?(Hammock::Constants::ImpliedUnsafeActions)
89
- end
90
-
91
- private
92
-
93
- def implied_verb? verb
94
- verb.in? :index, :create, :show, :update, :destroy
95
- end
96
-
97
- def raise_unless_setup_while_trying_to task
98
- raise "You have to call for(verb, entity) (and optionally within(parent)) on this HammockRoutePiece before you can #{task}." unless setup?
99
- end
100
-
101
- def param_str params
102
- link_params = entity.record? ? entity.unsaved_attributes.merge(params || {}) : params
103
-
104
- if link_params.blank?
105
- ''
106
- else
107
- '?' + {entity.base_model => link_params}.to_query
108
- end
109
- end
110
-
111
- end
112
-
113
- DefaultRecordVerbs = {
114
- :show => :get,
115
- :edit => :get,
116
- :update => :put,
117
- :destroy => :delete
118
- }.freeze
119
- DefaultResourceVerbs = {
120
- :index => :get
121
- }.freeze
122
- DefaultBuildVerbs = {
123
- :new => :get,
124
- :create => :post
125
- }.freeze
126
-
127
- attr_reader :mdl, :parent, :children, :record_routes, :resource_routes, :build_routes
128
-
129
- def initialize entity = nil, options = {}
130
- @mdl = entity if entity.is_a?(Symbol)
131
- @parent = options[:parent]
132
- @children = {}
133
- define_routes options
134
- end
135
-
136
- def ancestry
137
- parent.nil? ? [] : parent.ancestry.push(self)
138
- end
139
-
140
- def for verb, entities, options
141
- raise "HammockResource#for requires an explicitly specified verb as its first argument." unless verb.is_a?(Symbol)
142
- raise "You have to supply at least one record or resource." if entities.empty?
143
-
144
- entity = entities.shift
145
-
146
- if entities.empty?
147
- piece_for verb, entity
148
- else
149
- children[entity.resource_sym].for(verb, entities, options).within piece_for(nil, entity)
150
- end
151
- end
152
-
153
- def add entity, options, steps = nil
154
- if steps.nil?
155
- add entity, options, (options[:name_prefix] || '').chomp('_').split('_').map {|i| i.pluralize.underscore.to_sym }
156
- elsif steps.empty?
157
- add_child entity, options
158
- else
159
- children[steps.shift].add entity, options, steps
160
- end
161
- end
162
-
163
- def routeable_as verb, entity
164
- if entity.record? && record_routes[verb || :show]
165
- :record
166
- elsif entity.resource? && resource_routes[verb || :index]
167
- :resource
168
- elsif !verb.nil? && build_routes[verb]
169
- :build
170
- end
171
- end
172
-
173
- private
174
-
175
- def define_routes options
176
- @record_routes = DefaultRecordVerbs.dup.update(options[:member] || {})
177
- @resource_routes = DefaultResourceVerbs.dup.update(options[:collection] || {})
178
- @build_routes = DefaultBuildVerbs.dup.update(options[:build] || {})
179
- end
180
-
181
- def add_child entity, options
182
- child = HammockResource.new entity, options.merge(:parent => self)
183
- children[child.mdl] = child
184
- end
185
-
186
- def piece_for verb, entity
187
- child = children[entity.resource_sym]
188
-
189
- if child.nil?
190
- raise "No routes are defined for #{entity.resource}#{' within ' + ancestry.map {|r| r.mdl.to_s }.join(', ') unless ancestry.empty?}."
191
- else
192
- HammockRoutePiece.new(child).for(verb, entity)
193
- end
194
- end
195
-
23
+ self.route_map = Hammock::RouteNode.new
196
24
  end
197
25
 
198
26
  end
@@ -15,10 +15,10 @@ module Hammock
15
15
  find_record :find_with_deleted
16
16
  end
17
17
 
18
- def find_record finder = :find
18
+ def find_record
19
19
  result = if !callback(:before_find)
20
20
  # callbacks failed
21
- elsif (record = retrieve_record(finder)).nil?
21
+ elsif (record = retrieve_record).nil?
22
22
  log "#{mdl}<#{params[:id]}> doesn't exist within #{requester_name.possessive} #{action_name} scope."
23
23
  :not_found
24
24
  elsif :ok != (verbability = can_verb_record?(action_name.to_sym, record))
@@ -44,11 +44,11 @@ module Hammock
44
44
  end
45
45
  end
46
46
 
47
- def retrieve_record finder
47
+ def retrieve_record
48
48
  if (scope = current_scope).nil?
49
49
 
50
50
  else
51
- record = scope.send finder, :first, :conditions => {find_column_name => params[:id]}
51
+ record = scope.send :find, :first, :conditions => {mdl.routing_attribute => params[:id]}
52
52
  record || required_callback(:after_failed_find)
53
53
  end
54
54
  end
@@ -142,7 +142,7 @@ module Hammock
142
142
 
143
143
  def find_record_on_create
144
144
  if findable_on_create?
145
- if record = nest_scope.find(:first, :conditions => params_for(mdl.symbolize))
145
+ if record = current_nest_scope.find(:first, :conditions => params_for(mdl.symbolize))
146
146
  log "suitable record already exists: #{record}"
147
147
  assign_entity record
148
148
  else
@@ -29,8 +29,10 @@ module Hammock
29
29
  @hammock_cached_mdl_name ||= self.class.to_s.sub('Controller', '').singularize.underscore
30
30
  end
31
31
 
32
- def current_route
33
- @hammock_cached_current_route ||= route_for(action_name.to_sym, *current_nested_records.push(@entity)) unless @entity.nil?
32
+ # Returns the node in the Hammock routing map corresponding to the (possibly nested) resource handling the current request.
33
+ def current_hammock_resource
34
+ nesting_resources = params.keys.select {|k| /_id$/ =~ k }.map {|k| k.gsub(/_id$/, '').pluralize }
35
+ ActionController::Routing::Routes.route_map.base_for nesting_resources.push(mdl.resource_name).map(&:to_sym)
34
36
  end
35
37
 
36
38
  # Returns true if the current action represents an edit on +record+.
@@ -57,7 +59,7 @@ module Hammock
57
59
  elsif record_or_records.is_a? ActiveRecord::Base
58
60
  instance_variable_set "@#{mdl_name}", (@record = record_or_records)
59
61
  elsif record_or_records.is_a? Ambition::Context
60
- # log "Unkicked query: #{record_or_records.to_s}"
62
+ log "Unkicked query: #{record_or_records.to_hash.inspect}"
61
63
  instance_variable_set "@#{mdl_name.pluralize}", (@records = record_or_records)
62
64
  elsif record_or_records.is_a? Array
63
65
  instance_variable_set "@#{mdl_name.pluralize}", (@records = record_or_records)
@@ -65,15 +67,25 @@ module Hammock
65
67
  raise "Unknown record(s) type #{record_or_records.class}."
66
68
  end
67
69
 
68
- if assign_nestable_resources
69
- @entity
70
- else
71
- escort :not_found
72
- end
70
+ @entity if assign_nesting_entities
73
71
  end
74
72
 
75
73
  private
76
74
 
75
+ def assign_nesting_entities
76
+ results = nesting_scope_list.map &:kick
77
+
78
+ if results.all? {|records| records.length == 1 }
79
+ results.map {|records|
80
+ records.first
81
+ }.all? {|record|
82
+ log "nested record #{record.concise_inspect} assigned to @#{record.base_model}."
83
+ add_nested_entity record
84
+ instance_variable_set "@#{record.base_model}", record
85
+ }
86
+ end
87
+ end
88
+
77
89
  def make_new_record resource = mdl
78
90
  resource.new_with(params_for(resource.symbolize))
79
91
  end
@@ -92,39 +104,24 @@ module Hammock
92
104
  end
93
105
  end
94
106
 
95
- def assign_nestable_resources
96
- @current_nested_records, @current_nested_resources = [], []
97
- params.symbolize_keys.dragnet(*nestable_resources.keys).all? {|param_name,column_name|
98
- constant_name = param_name.to_s.sub(/_id$/, '').camelize
99
- constant = Object.const_get constant_name rescue nil
100
-
101
- if constant.nil?
102
- log "'#{constant_name}' is not available for #{param_name}."
103
- elsif (record = constant.find_by_id(params[param_name])).nil?
104
- log "#{constant}<#{params[param_name]}> not found."
105
- else
106
- @current_nested_records << record
107
- @current_nested_resources << record.class
108
- @record.send "#{nestable_resources[param_name]}=", params[param_name] unless @record.nil?
109
- # log "Assigning @#{constant.name.underscore} with #{record.inspect}."
110
- instance_variable_set "@#{constant_name.underscore}", record
111
- end
112
- }
113
- end
114
-
115
107
  def current_nested_records
116
- @current_nested_records.nil? ? [] : @current_nested_records.dup
108
+ (@current_nested_records || []).dup
117
109
  end
118
110
 
119
111
  def current_nested_resources
120
- @current_nested_resources.nil? ? [] : @current_nested_resources.dup
112
+ (@current_nested_resources || []).dup
113
+ end
114
+
115
+ def add_nested_entity entity
116
+ (@current_nested_records ||= []).push entity
117
+ (@current_nested_resources ||= []).push entity.resource
121
118
  end
122
119
 
123
120
  def nested_within? record_or_resource
124
121
  if record_or_resource.is_a? ActiveRecord::Base
125
- @current_nested_records.include? record_or_resource
122
+ current_nested_records.include? record_or_resource
126
123
  else
127
- @current_nested_resources.include? record_or_resource
124
+ current_nested_resources.include? record_or_resource
128
125
  end
129
126
  end
130
127
 
@@ -136,8 +133,15 @@ module Hammock
136
133
  @editing = @record
137
134
  end
138
135
 
136
+ # TODO process /^creating_\w+_id$/ as well
139
137
  def set_creator_id_if_appropriate
140
- @record.creator_id = @current_account.id if @record.respond_to?(:creator_id=)
138
+ if @record.respond_to?(:creator_id=)
139
+ if @current_account.nil?
140
+ log "Warning: @#{@record.base_model}.creator_id isn't being set, since @current_account was nil."
141
+ else
142
+ @record.creator_id = @current_account.id
143
+ end
144
+ end
141
145
  end
142
146
 
143
147
  def partial_exists? name, extension = nil
@@ -47,7 +47,7 @@ module Hammock
47
47
  def nested_route_for *resources
48
48
  resources.delete_if &:nil?
49
49
  requested_verb = resources.shift if resources.first.is_a?(Symbol)
50
- args = @current_nested_records.dup.concat(resources)
50
+ args = current_nested_records.concat(resources)
51
51
 
52
52
  args.unshift(requested_verb) unless requested_verb.nil?
53
53
  route_for *args
@@ -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 "You have to supply an Array of at least one record or resource." if entities.empty? unless 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
@@ -62,7 +62,7 @@ module Hammock
62
62
  end
63
63
  end
64
64
 
65
- def verb_scope
65
+ def current_verb_scope
66
66
  if @current_account && (scope_name = account_verb_scope?)
67
67
  # log "got an account_verb_scope #{scope_name}."
68
68
  mdl.send scope_name, @current_account
@@ -75,17 +75,24 @@ module Hammock
75
75
  end
76
76
  end
77
77
 
78
- def nest_scope
79
- params.symbolize_keys.dragnet(*nestable_resources.keys).inject(mdl.ambition_context) {|acc,(k,v)|
80
- # TODO this would be more ductile if it used AR assocs instead of explicit FK
81
- eval "acc.select {|r| r.#{nestable_resources[k]} == v }"
82
- }
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 }
83
84
  end
84
85
 
85
86
  def current_scope
86
- if (resultant_scope = nest_scope.chain(verb_scope)).nil?
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?
87
91
  nil
88
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}"
89
96
  resultant_scope = resultant_scope.chain(custom_scope) unless custom_scope.nil?
90
97
  resultant_scope.sort_by &mdl.sorter
91
98
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: benhoskings-hammock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Hoskings
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-27 00:00:00 -08:00
12
+ date: 2009-03-05 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -63,6 +63,7 @@ files:
63
63
  - hammock.gemspec
64
64
  - lib/hammock.rb
65
65
  - lib/hammock/ajaxinate.rb
66
+ - lib/hammock/callback.rb
66
67
  - lib/hammock/callbacks.rb
67
68
  - lib/hammock/canned_scopes.rb
68
69
  - lib/hammock/constants.rb
@@ -91,6 +92,8 @@ files:
91
92
  - lib/hammock/restful_support.rb
92
93
  - lib/hammock/route_drawing_hooks.rb
93
94
  - lib/hammock/route_for.rb
95
+ - lib/hammock/route_node.rb
96
+ - lib/hammock/route_step.rb
94
97
  - lib/hammock/scope.rb
95
98
  - lib/hammock/suggest.rb
96
99
  - lib/hammock/utils.rb
@@ -98,7 +101,6 @@ files:
98
101
  - misc/template.rb
99
102
  - tasks/hammock_tasks.rake
100
103
  - test/hammock_test.rb
101
- - test/rails_root/test/test_helper.rb
102
104
  has_rdoc: true
103
105
  homepage: http://github.com/benhoskings/hammock
104
106
  post_install_message:
@@ -126,5 +128,5 @@ rubygems_version: 1.2.0
126
128
  signing_key:
127
129
  specification_version: 2
128
130
  summary: Hammock is a Rails plugin that eliminates redundant code in a very RESTful manner
129
- test_files:
130
- - test/rails_root/test/test_helper.rb
131
+ test_files: []
132
+