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.