Rails 3.1 route constraint fails
This question is comparable to this one, but I ask it again because the answer provided does not solve the issue, and as the person asking that question concludes: it might be a bug in Rails (but no follow up on that is given).
I have these routes:
resources :books, controller: :projects, type: "Book" do
resources "", as: :book_chapters, controller: :contributions, type: "BookChapter", except: :index, constraints: IdConstraint
end
This is the IdConstraint:
class IdConstraint
INVALID_IDS = %w[edit series new]
def self.matches?(request)
!INVALID_IDS.include? request.params[:id]
end
end
I use friedly_id, so the :id
parameter is a string based on the title of a book or book chapter.
Now a request like /books/friendly-开发者_Go百科id-of-book/friendly-id-of-chapter
routes to the show
action on the book_chapters controller.
But I would also expect /books/friendly-id-of-book/edit
to route to the edit
action on the books controller, because the constraint on the book_chapters route exclude edit
as id. This does not work, and it seems that the problem is in IdConstraint
. If I replace the one line in the matches?
method with false
:
class IdConstraint
INVALID_IDS = %w[edit series new]
def self.matches?(request)
false
end
end
the .../edit
route properly routes to the edit action on the books controller.
But when i only add a line with false
after the original line:
class IdConstraint
INVALID_IDS = %w[edit series new]
def self.matches?(request)
!INVALID_IDS.include? request.params[:id]
false
end
end
the route fails, i.e. it routes to the book_chapters controller with id "edit", while I would really expect it to still return false, and thus route to the edit action of the books controller.
I can't figure out what's going wrong here. Any ideas?
It looks to me like what you're running up against is a scope issue. INVALID_IDS is defined outside self.matches?(), so it's not available inside self.matches?().
You could try:
class IdConstraint
def self.matches?(request)
INVALID_IDS = %w[edit series new]
!INVALID_IDS.include? request.params[:id]
end
end
Or, if you really need INVALID_IDS to be available elsewhere in IdConstraint, you can do:
# Note that you'll need to use IdConstraint.new as your constraint for
# this one, not IdConstraint
class IdConstraint
def initialize
@INVALID_IDS = %w[edit series new]
end
def matches?(request)
!@INVALID_IDS.include? request.params[:id]
end
end
There's a good example of the second method on the Rails Guide.
UPDATE
Yeah, you're right about the bug with request.params. For now, this seems to work relatively well:
class IdConstraint
INVALID_IDS = %w[edit series new]
def self.matches?(request)
end_of_path = request.path.split('/').last
!INVALID_IDS.include? end_of_path
end
end
This is caused by a bug in Rails 3.1 master (at 0e19c7c414bb). See the bug report on GitHub. Until it is fixed you can temporarily circumvent it by using request.path_parameters[:id]
instead of request.params[:id]
.
精彩评论