Resource routing allows you to quickly declare all of the common routes for a given resourceful controller. Instead of declaring separate routes for your index, show, new, edit, create, update and destroy actions, a resourceful route declares them in a single line of code:
resources :photos
Sometimes, you have a resource that clients always look up without referencing an ID. A common example, /profile always shows the profile of the currently logged in user. In this case, you can use a singular resource to map /profile (rather than /profile/:id) to the show action.
resource :profile
It’s common to have resources that are logically children of other resources:
resources :magazines do
resources :ads
end
You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an admin namespace. You would place these controllers under the app/controllers/admin directory, and you can group them together in your router:
namespace "admin" do
resources :posts, :comments
end
CANONICAL_ACTIONS holds all actions that does not need a prefix or a path appended since they fit properly in their scope level.
To add a route to the collection:
resources :photos do
collection do
get 'search'
end
end
This will enable Rails to recognize paths such as /photos/search with GET, and route to the search action of PhotosController. It will also create the search_photos_url and search_photos_path route helpers.
# File lib/action_dispatch/routing/mapper.rb, line 985
985: def collection
986: unless resource_scope?
987: raise ArgumentError, "can't use collection outside resource(s) scope"
988: end
989:
990: with_scope_level(:collection) do
991: scope(parent_resource.collection_scope) do
992: yield
993: end
994: end
995: end
# File lib/action_dispatch/routing/mapper.rb, line 1072
1072: def match(*args)
1073: options = args.extract_options!.dup
1074: options[:anchor] = true unless options.key?(:anchor)
1075:
1076: if args.length > 1
1077: args.each { |path| match(path, options.dup) }
1078: return self
1079: end
1080:
1081: on = options.delete(:on)
1082: if VALID_ON_OPTIONS.include?(on)
1083: args.push(options)
1084: return send(on){ match(*args) }
1085: elsif on
1086: raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1087: end
1088:
1089: if @scope[:scope_level] == :resources
1090: args.push(options)
1091: return nested { match(*args) }
1092: elsif @scope[:scope_level] == :resource
1093: args.push(options)
1094: return member { match(*args) }
1095: end
1096:
1097: action = args.first
1098: path = path_for_action(action, options.delete(:path))
1099:
1100: if action.to_s =~ /^[\w\/]+$/
1101: options[:action] ||= action unless action.to_s.include?("/")
1102: else
1103: action = nil
1104: end
1105:
1106: if options.key?(:as) && !options[:as]
1107: options.delete(:as)
1108: else
1109: options[:as] = name_for_action(options[:as], action)
1110: end
1111:
1112: super(path, options)
1113: end
To add a member route, add a member block into the resource block:
resources :photos do
member do
get 'preview'
end
end
This will recognize /photos/1/preview with GET, and route to the preview action of PhotosController. It will also create the preview_photo_url and preview_photo_path helpers.
# File lib/action_dispatch/routing/mapper.rb, line 1008
1008: def member
1009: unless resource_scope?
1010: raise ArgumentError, "can't use member outside resource(s) scope"
1011: end
1012:
1013: with_scope_level(:member) do
1014: scope(parent_resource.member_scope) do
1015: yield
1016: end
1017: end
1018: end
# File lib/action_dispatch/routing/mapper.rb, line 1054
1054: def namespace(path, options = {})
1055: if resource_scope?
1056: nested { super }
1057: else
1058: super
1059: end
1060: end
# File lib/action_dispatch/routing/mapper.rb, line 1032
1032: def nested
1033: unless resource_scope?
1034: raise ArgumentError, "can't use nested outside resource(s) scope"
1035: end
1036:
1037: with_scope_level(:nested) do
1038: if shallow?
1039: with_exclusive_scope do
1040: if @scope[:shallow_path].blank?
1041: scope(parent_resource.nested_scope, nested_options) { yield }
1042: else
1043: scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
1044: scope(parent_resource.nested_scope, nested_options) { yield }
1045: end
1046: end
1047: end
1048: else
1049: scope(parent_resource.nested_scope, nested_options) { yield }
1050: end
1051: end
1052: end
# File lib/action_dispatch/routing/mapper.rb, line 1020
1020: def new
1021: unless resource_scope?
1022: raise ArgumentError, "can't use new outside resource(s) scope"
1023: end
1024:
1025: with_scope_level(:new) do
1026: scope(parent_resource.new_scope(action_path(:new))) do
1027: yield
1028: end
1029: end
1030: end
Sometimes, you have a resource that clients always look up without referencing an ID. A common example, /profile always shows the profile of the currently logged in user. In this case, you can use a singular resource to map /profile (rather than /profile/:id) to the show action:
resource :geocoder
creates six different routes in your application, all mapping to the GeoCoders controller (note that the controller is named after the plural):
GET /geocoder/new POST /geocoder GET /geocoder GET /geocoder/edit PUT /geocoder DELETE /geocoder
# File lib/action_dispatch/routing/mapper.rb, line 890
890: def resource(*resources, &block)
891: options = resources.extract_options!
892:
893: if apply_common_behavior_for(:resource, resources, options, &block)
894: return self
895: end
896:
897: resource_scope(SingletonResource.new(resources.pop, options)) do
898: yield if block_given?
899:
900: collection do
901: post :create
902: end if parent_resource.actions.include?(:create)
903:
904: new do
905: get :new
906: end if parent_resource.actions.include?(:new)
907:
908: member do
909: get :edit if parent_resource.actions.include?(:edit)
910: get :show if parent_resource.actions.include?(:show)
911: put :update if parent_resource.actions.include?(:update)
912: delete :destroy if parent_resource.actions.include?(:destroy)
913: end
914: end
915:
916: self
917: end
In Rails, a resourceful route provides a mapping between HTTP verbs and URLs and controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as
resources :photos
creates seven different routes in your application, all mapping to the Photos controller:
GET /photos/new POST /photos GET /photos/:id GET /photos/:id/edit PUT /photos/:id DELETE /photos/:id
Allows you to change the paths of the seven default actions. Paths not specified are not changed.
resources :posts, :path_names => { :new => "brand_new" }
The above example will now change /posts/new to /posts/brand_new
# File lib/action_dispatch/routing/mapper.rb, line 943
943: def resources(*resources, &block)
944: options = resources.extract_options!
945:
946: if apply_common_behavior_for(:resources, resources, options, &block)
947: return self
948: end
949:
950: resource_scope(Resource.new(resources.pop, options)) do
951: yield if block_given?
952:
953: collection do
954: get :index if parent_resource.actions.include?(:index)
955: post :create if parent_resource.actions.include?(:create)
956: end
957:
958: new do
959: get :new
960: end if parent_resource.actions.include?(:new)
961:
962: member do
963: get :edit if parent_resource.actions.include?(:edit)
964: get :show if parent_resource.actions.include?(:show)
965: put :update if parent_resource.actions.include?(:update)
966: delete :destroy if parent_resource.actions.include?(:destroy)
967: end
968: end
969:
970: self
971: end
# File lib/action_dispatch/routing/mapper.rb, line 868
868: def resources_path_names(options)
869: @scope[:path_names].merge!(options)
870: end
# File lib/action_dispatch/routing/mapper.rb, line 1115
1115: def root(options={})
1116: if @scope[:scope_level] == :resources
1117: with_scope_level(:root) do
1118: scope(parent_resource.path) do
1119: super(options)
1120: end
1121: end
1122: else
1123: super(options)
1124: end
1125: end
# File lib/action_dispatch/routing/mapper.rb, line 1163
1163: def action_options?(options)
1164: options[:only] || options[:except]
1165: end
# File lib/action_dispatch/routing/mapper.rb, line 1247
1247: def action_path(name, path = nil)
1248: path || @scope[:path_names][name.to_sym] || name.to_s
1249: end
# File lib/action_dispatch/routing/mapper.rb, line 1133
1133: def apply_common_behavior_for(method, resources, options, &block)
1134: if resources.length > 1
1135: resources.each { |r| send(method, r, options, &block) }
1136: return true
1137: end
1138:
1139: if resource_scope?
1140: nested { send(method, resources.pop, options, &block) }
1141: return true
1142: end
1143:
1144: options.keys.each do |k|
1145: (options[:constraints] ||= {})[k] = options.delete(k) if options[k].is_a?(Regexp)
1146: end
1147:
1148: scope_options = options.slice!(*RESOURCE_OPTIONS)
1149: unless scope_options.empty?
1150: scope(scope_options) do
1151: send(method, resources.pop, options, &block)
1152: end
1153: return true
1154: end
1155:
1156: unless action_options?(options)
1157: options.merge!(scope_action_options) if scope_action_options?
1158: end
1159:
1160: false
1161: end
# File lib/action_dispatch/routing/mapper.rb, line 1228
1228: def canonical_action?(action, flag)
1229: flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
1230: end
# File lib/action_dispatch/routing/mapper.rb, line 1224
1224: def id_constraint
1225: @scope[:constraints][:id]
1226: end
# File lib/action_dispatch/routing/mapper.rb, line 1220
1220: def id_constraint?
1221: @scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp)
1222: end
# File lib/action_dispatch/routing/mapper.rb, line 1259
1259: def name_for_action(as, action)
1260: prefix = prefix_name_for_action(as, action)
1261: prefix = Mapper.normalize_name(prefix) if prefix
1262: name_prefix = @scope[:as]
1263:
1264: if parent_resource
1265: collection_name = parent_resource.collection_name
1266: member_name = parent_resource.member_name
1267: end
1268:
1269: name = case @scope[:scope_level]
1270: when :nested
1271: [member_name, prefix]
1272: when :collection
1273: [prefix, name_prefix, collection_name]
1274: when :new
1275: [prefix, :new, name_prefix, member_name]
1276: when :member
1277: [prefix, shallow_scoping? ? @scope[:shallow_prefix] : name_prefix, member_name]
1278: when :root
1279: [name_prefix, collection_name, prefix]
1280: else
1281: [name_prefix, member_name, prefix]
1282: end
1283:
1284: candidate = name.select(&:present?).join("_").presence
1285: candidate unless as.nil? && @set.routes.find { |r| r.name == candidate }
1286: end
# File lib/action_dispatch/routing/mapper.rb, line 1213
1213: def nested_options
1214: {}.tap do |options|
1215: options[:as] = parent_resource.member_name
1216: options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
1217: end
1218: end
# File lib/action_dispatch/routing/mapper.rb, line 1236
1236: def path_for_action(action, path)
1237: prefix = shallow_scoping? ?
1238: "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
1239:
1240: path = if canonical_action?(action, path.blank?)
1241: prefix.to_s
1242: else
1243: "#{prefix}/#{action_path(action, path)}"
1244: end
1245: end
# File lib/action_dispatch/routing/mapper.rb, line 1251
1251: def prefix_name_for_action(as, action)
1252: if as
1253: as.to_s
1254: elsif !canonical_action?(action, @scope[:scope_level])
1255: action.to_s
1256: end
1257: end
# File lib/action_dispatch/routing/mapper.rb, line 1179
1179: def resource_method_scope?
1180: [:collection, :member, :new].include?(@scope[:scope_level])
1181: end
# File lib/action_dispatch/routing/mapper.rb, line 1205
1205: def resource_scope(resource)
1206: with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
1207: scope(parent_resource.resource_scope) do
1208: yield
1209: end
1210: end
1211: end
# File lib/action_dispatch/routing/mapper.rb, line 1175
1175: def resource_scope?
1176: [:resource, :resources].include?(@scope[:scope_level])
1177: end
# File lib/action_dispatch/routing/mapper.rb, line 1171
1171: def scope_action_options
1172: @scope[:options].slice(:only, :except)
1173: end
# File lib/action_dispatch/routing/mapper.rb, line 1167
1167: def scope_action_options?
1168: @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except])
1169: end
# File lib/action_dispatch/routing/mapper.rb, line 1232
1232: def shallow_scoping?
1233: shallow? && @scope[:scope_level] == :member
1234: end
# File lib/action_dispatch/routing/mapper.rb, line 1183
1183: def with_exclusive_scope
1184: begin
1185: old_name_prefix, old_path = @scope[:as], @scope[:path]
1186: @scope[:as], @scope[:path] = nil, nil
1187:
1188: with_scope_level(:exclusive) do
1189: yield
1190: end
1191: ensure
1192: @scope[:as], @scope[:path] = old_name_prefix, old_path
1193: end
1194: end
# File lib/action_dispatch/routing/mapper.rb, line 1196
1196: def with_scope_level(kind, resource = parent_resource)
1197: old, @scope[:scope_level] = @scope[:scope_level], kind
1198: old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
1199: yield
1200: ensure
1201: @scope[:scope_level] = old
1202: @scope[:scope_level_resource] = old_resource
1203: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.