Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Refactorize and enhance container path methods
  • Loading branch information
atd committed Feb 16, 2010
1 parent 2f169a8 commit 98ce208
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 27 deletions.
83 changes: 63 additions & 20 deletions lib/action_controller/station.rb
Expand Up @@ -47,16 +47,29 @@ def model_class
self.class.model_class
end

# Obtains a given ActiveRecord from parameters.
# Obtains a given ActiveRecord instance from parameters.
#
# Returns the first of records_from_path. Same options.
def record_from_path(options = {})
records_from_path(options).first
end

# Obtains all ActiveRecord record in parameters.
#
# Given this URI:
# /projects/1/tasks/2/posts/3
#
# records_from_path #=> [ Project-1, Task-2 ]
#
# Options:
# * acts_as: the ActiveRecord model must acts_as the given symbol.
# acts_as => :container
def record_from_path(options = {})
def records_from_path(options = {})
acts_as_module = "ActiveRecord::#{ options[:acts_as].to_s.classify }".constantize if options[:acts_as]

candidates = params.keys.select{ |k| k[-3..-1] == '_id' }

candidates.each do |candidate_key|
candidates.map { |candidate_key|
# Filter keys that correspond to classes
begin
candidate_class = candidate_key[0..-4].to_sym.to_class
Expand All @@ -73,51 +86,71 @@ def record_from_path(options = {})

# Find record
begin
record = candidate_class.find_with_param(params[candidate_key])
instance_variable_set("@#{ options[:acts_as] }", record) if options[:acts_as]
instance_variable_set("@#{ candidate_class.to_s.underscore }", record)
return record
candidate_class.find_with_param(params[candidate_key])
rescue ::ActiveRecord::RecordNotFound
next
end
end

nil
}.compact
end

# An instance modeling site configuration and stage
def current_site
@current_site ||= Site.current
end

# Find current Container using path from the request
def path_container
@path_container ||= record_from_path(:acts_as => :container)
# Find all Containers in the path, using records_from_path
#
# Options:
# ancestors:: include containers's containers
# type:: the class of the searched containers
def path_containers(options = {})
@path_containers ||= records_from_path(:acts_as => :container)

candidates = options[:ancestors] ?
@path_containers.map{ |c| c.container_and_ancestors }.flatten.uniq :
@path_containers.dup

filter_type(candidates, options[:type])
end

# Find current Container using path from the request
#
# Note that in StationResouces this method is redefined priorizing the resource
def current_container
path_container
# Uses path_containers. Same options.
def path_container(options = {})
path_containers(options).first
end

# Must find a Container
#
# Calls path_container to figure out from params. If unsuccesful,
# raises ActiveRecord::RecordNotFound
#
def path_container!
path_container || raise(ActiveRecord::RecordNotFound)
def path_container!(options = {})
path_container(options) || raise(ActiveRecord::RecordNotFound)
end

# Find current Container using path from the request
#
# Note that in StationResouces this method is redefined looking also at the resource
#
# Options:
# type:: the class the container should be
# path_ancestors:: include the ancestors of the resources in the path
def current_container(options = {})
options[:ancestors] = options.delete(:path_ancestors)

path_container(options)
end

# Must find a Container
#
# Calls current_container to figure out from params. If unsuccesful,
# raises ActiveRecord::RecordNotFound
#
# Takes the same options as current_container
#
def current_container!
current_container || raise(ActiveRecord::RecordNotFound)
def current_container!(options = {})
current_container(options) || raise(ActiveRecord::RecordNotFound)
end

protected
Expand All @@ -142,5 +175,15 @@ def set_params_from_raw_post(content = controller_name.singularize.to_sym)
params[content][:media] ||= file
params[content][:public_read] ||= true
end

private

def filter_type(candidates, type) #:nodoc:
return candidates.dup unless type

type = type.to_sym.to_class

candidates.select{ |c| c.is_a?(type) }
end
end
end
59 changes: 52 additions & 7 deletions lib/action_controller/station_resources.rb
Expand Up @@ -2,6 +2,9 @@ module ActionController #:nodoc:
# Controller methods for Resources
#
module StationResources
class ContainerError < ::StandardError #:nodoc:
end

class << self
def included(base) #:nodoc:
base.send :include, ActionController::Station unless base.ancestors.include?(ActionController::Station)
Expand All @@ -11,6 +14,9 @@ def included(base) #:nodoc:
alias_method controller_name.singularize, :resource # alias_method :article, :resource
helper_method controller_name.singularize # helper_method :article
end # end

base.send :rescue_from, ContainerError, :with => :container_error

base.send :include, ActionController::Authorization unless base.ancestors.include?(ActionController::Authorization)
end
end
Expand Down Expand Up @@ -258,18 +264,52 @@ def resources
model_class.roots.in(path_container).column_sort(params[:order], params[:direction]).paginate(:page => params[:page], :conditions => @conditions)
end

# Find current Container prioritizing the resource (if it exists), or its container.
# Search in the resource's containers and in the path for current Container.
#
# We start with the following assumptions:
# * Resources have a unique branch of nested containers in the containers tree
# * If we find containers of the same type in both branches (resource containers
# and path), they must be the same. If they aren't we assume a routing error (409 Conflict).
#
# Defaults to path_container
def current_container
@current_container ||=
resource && ( resource.class.acts_as?(:container) && resource ||
resource.respond_to?(:container) && resource.container ) ||
path_container
# Options:
# type:: the class of the container
def current_container(options = {})
find_current_container(options)
end

private

def find_current_container(options) #:nodoc:
rc = resource_container(options)

path_options = options.dup
path_options[:ancestors] = path_options.delete(:path_ancestors)
pc = path_container(path_options)

if rc.present? && pc.present? &&
rc != pc
raise ContainerError
end

rc || pc
end

# Gets a container from the resource containers
def resource_container(options = {}) #:nodoc:
return nil unless resource.present?

@resource_container_candidates ||=
Array( resource.class.acts_as?(:container) ?
resource.container_and_ancestors :
resource.class.acts_as?(:content) ?
resource.container.try(:container_and_ancestors) :
nil ).compact

candidates = filter_type(@resource_container_candidates, options[:type])

candidates.first
end

# Redirect here after create if everythig went well
def after_create_with_success
redirect_to @resource
Expand Down Expand Up @@ -304,5 +344,10 @@ def after_destroy_with_success
def after_destroy_with_errors
redirect_to(request.referer || [ path_container, model_class.new ])
end

def container_error(e) #:nodoc:
render :text => 'Container route conflicts with resource container',
:status => 409
end
end
end
8 changes: 8 additions & 0 deletions lib/active_record/container.rb
Expand Up @@ -50,6 +50,14 @@ def contents(options = {})

ActiveRecord::Content::Inquirer.all(options, container_options)
end

# A list of all the nested containers of this Container, including self,
# sorted by closeness
def container_and_ancestors
ca = respond_to?(:container) && container.try(:container_and_ancestors) || nil

(Array(self) + Array(ca)).compact
end
end
end
end

0 comments on commit 98ce208

Please sign in to comment.