为什么在验证帖子标题后,当我遇到验证错误时,浏览器会将帖子标题的先前值传递给 Rails 服务器



我通过 :title 而不是 :id 获得帖子

routes.rb:

Rails.application.routes.draw do
#RSS
get 'feed' => 'posts#feed'
get 'archive' => 'posts#archive'
  devise_for :users, controllers: { omniauth_callbacks: "callbacks" }
  root to: "posts#index"
  resources :posts, param: :title do
    resources :comments, only: [:new, :create, :destroy]
    resources  :images do
      resources :comments, only: [:new, :create]
    end
    resources  :links do
      resources :comments, only: [:new, :create]
    end
    resources :photos, only: [:new, :create,:destroy]
    resources :songs, only: [:new, :create, :destroy]
  end

post_controller:

class PostsController < ApplicationController
  before_action :authenticate_user!, only: [:new, :create, :show]
  respond_to :html, :json, :rss, :atom
  def index
    if params[:search].blank?
      @posts = Post.includes(:comments, :photos).all
    else
      @search = Post.search do
        fulltext params[:search]
      end
      @posts = @search.results
    end
    respond_with(@posts)
  end
  def show
    set_post
  end
  def new
    @post = Post.new
  end
  def edit
    @post = Post.find_by(title: params[:title])
  end
  def create
    @post = Post.new(post_params)
    @post.errors.add(:base, :invalid) unless @post.save
    respond_with(@post)
  end
  def update
    set_post # this @post dont get post becouse browser pass wrong title and rails dont find it
    if @post.valid? # here error becouse @post hasnt find
        @post.update(post_params)
    else
      @post.errors.add(:base, :invalid)
    end
     respond_with(@post)
  end
  def destroy
    set_post
    @post.destroy
    respond_with(@post)
  end
  def feed
    @posts = Post.all.reverse
    respond_with(@posts)
  end
  def archive
    @posts_by_year = Post.limit(300).all.order("created_at DESC").
    group_by {|post| post.created_at.beginning_of_year}
  end
  private
  def set_post
    #fix N+1 queries and find by posts title
    @post = Post.includes(:comments, :photos, :links).find_by(title: params[:title])
  end

  def post_params
    params.require(:post).permit(:title, :content)
  end
end

当我创建新帖子时,如果我在帖子标题中包含点,我会从 Rails 错误中得到。因此,我在这种情况下使用验证格式:方法。

帖子.rb:

class Post < ActiveRecord::Base
  #overwrite the to_param for changing routes from posts/id to posts/title
  def to_param
    title
  end
  has_many :comments, dependent: :destroy, as: :commentable
  has_many :images,   dependent: :destroy
  has_many :links,    dependent: :destroy
  has_many :photos,   dependent: :destroy
  has_many :songs,    dependent: :destroy
  validates :content, presence: true
  validates :title, presence: true, length: { maximum: 100 }
  validates :title, format: { without: /./,
                              message: "must be without dot" }
  searchable do
    text :title, :content
  end
end

在此之后,当我更新帖子时,我的验证格式方法有效,我收到验证消息"必须没有点"。井。我删除了帖子标题输入字段中的点并提交表单。现在浏览器发送到服务器以前的帖子标题值与点。

Started PATCH "/posts/my%20test%20post%20title%20with%20dot." for 127.0.0.1 at 2017-01-26 11:57:34 +0300
ActionController::RoutingError (No route matches [PATCH] "/posts/my%20test%20post%20title%20with%20dot."):

因此,rails 无法按标题找到带有点的帖子,我收到错误。我能解决这个问题吗?也许在这种情况下:title而不是:id是个坏主意?

我假设你正在使用form_for? Rails 将在帖子对象上设置标题,然后运行验证。 当它失败时,它将显示编辑视图,然后form_for将使用 to_param 方法来设置 url,这将使用帖子更新的标题。 当您尝试再次更新时,它将使用 url 中的标题来尝试查找帖子,但无法这样做,因为数据库中没有具有该标题的帖子。

你应该使用类似 friendly_id 的东西,但如果你真的想自己滚动,那么一个简单的实现是有一个根据标题设置的 slug 列after_validation但显然你必须确保它是独一无二的,所以我个人会切换到友好的 id 或其他处理 slugs 的宝石。

最新更新