module Resourcelogic module Urligence def self.included(klass) klass.helper_method :smart_url, :smart_path, :method_missing end def smart_url(*url_parts) url_params = url_parts.extract_options! url_parts.push(:url) url_parts.push(url_params) urligence(*url_parts) end def smart_path(*url_parts) url_params = url_parts.extract_options! url_parts.push(:path) url_parts.push(url_params) urligence(*url_parts) end private def urligence(*url_parts) url_parts = cleanup_url_parts(url_parts) method_name_fragments = extract_method_name_fragments(url_parts) method_arguments = extract_method_arguments(url_parts) send method_name_fragments.join("_"), *method_arguments end # The point of this method is to replace any object if a url param is passed. For example: # # [:admin, [:user, user_object], {:user_id => 4}] # # The "user_object" should be replaced by user with id 4, since we are explicitly saying we want to use User.find(4). # The last part is the url_params. # # This also pluralizes path names if the obj is nil. Example: # # [:user, nil] # # No param was passed to replace the nil object as in the example above, so it should be: # # :users # # This is needed for contextual development. Take modifying a resource from another, but a # parent resource is not needed. For example: # # payments/credit_cards # # You can manage and select a credit card from the credit cards resource but a payment object # is not needed. In a sense you are just creating a "payments" context. def cleanup_url_parts(url_parts) url_parts = url_parts.compact url_params = url_parts.last.is_a?(Hash) ? url_parts.last : {} new_url_parts = [] url_parts.each do |object| if !object.is_a?(Array) # It's not an array, just copy it over new_url_parts << object else klass = object.first.to_s.camelize.constantize rescue nil params_key = "#{object.first}_id".to_sym obj = if url_params.key?(params_key) if !klass url_params[params_key] elsif url_params[params_key] klass.find(url_params.delete(params_key)) end else object[1] end new_url_parts << [object.first, obj] end end new_url_parts end # Extracts the parts from the url_parts that make up the method name # # [:admin, [:user, user_object], :path] # # to: # # [:admin, :user, :path] def extract_method_name_fragments(url_parts) fragments = url_parts.collect do |obj| if obj.is_a?(Symbol) obj elsif obj.is_a?(Array) obj.first elsif !obj.is_a?(Hash) obj.class.name.underscore.to_sym end end fragments.compact end # Extracts the parts from url_parts that make up the method arguments # # [:admin, [:user, user_object], :path] # # to: # # [user_object] def extract_method_arguments(url_parts) url_parts.flatten.select { |obj| !obj.is_a?(Symbol) } end # Dynamically generate url methods for better flexibility. This allows us to not only call the standard urls: # # edit_object_path # object_path # # but also be able to call custom method paths: # # select_object_path # # Where select is a method added yourself: # # map.resources :users, :member => {:select => :any} def method_missing(method, *args, &block) if method.to_s =~ /^((.*)_)?(child_collection|parent_collection|sibling|sibling_collection)_(path|url)$/ || method.to_s =~ /^((.*)_)?(child|collection|object|parent)_(path|url)$/ action = $2.blank? ? nil : $2.to_sym target = $3 url_type = $4 send("smart_#{url_type}", *send("#{target}_url_parts", action, *args)) else super end end end end