开发者

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].

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜