如何在 RoR 中实现转发功能?

How to implement retweet functionality in RoR?

我正在尝试在我的应用程序上实现转推功能。

所以我的推文模型中有我的 retweet_id

推文架构

| user_id | content | created_at | updated_at | retweet_id

tweets.rb

belongs_to :user
has_many :retweets, class_name: 'Tweet', foreign_key: 'retweet_id'

user.rb

has_many :tweets

在我的推文控制器中

tweets_controller.rb

    ...


def retweet
    @retweet = Tweet.new(retweet_params)
      if @retweet.save
        redirect_to tweet_path, alert: 'Retweeted!'
      else
        redirect_to root_path, alert: 'Can not retweet'
      end
    end
    
    Private
    ...
    def retweet_params
      params.require(:retweet).permit(:retweet_id, :content).merge(user_id: current_user.id)
    end

在我看来

tweets/show.html.erb

<%= link_to 'Retweet', retweet_tweet_path(@tweet.id), method: :post %>

我的路线

resources :tweets do
  resources :comments
  resources :likes
  member do
    post :retweet
  end
end

所以当我尝试这个时我得到了一个错误

param is missing or the value is empty: retweet

所以我从 'retweet_params' 中删除了 .require 并删除了那个错误(虽然我不确定这是多么明智)

然后 link 可以工作但不会转发 - 恢复到我的操作中指定的后备 root_path。

Unpermitted parameters: :_method, :authenticity_token, :id
Redirected to http://localhost:3000/

我不确定我做错了什么。我怎样才能让我的转发工作?泰

现在您看到的错误是 Rails 中的强参数错误。如果您可以检查您的调试器或正在发送的 HTTP post 请求,您会发现您没有 retweet_params

中“需要”的参数
def retweet_params
  params.require(:retweet).permit(:retweet_id, :content).merge(user_id: current_user.id)
end

这实质上是说您期望像这样的参数嵌套哈希

params = { retweet: { id: 1, content: 'Tweet' } }

这行不通,因为您只是发送 ID。改用这样的东西怎么样?

TweetsController.rb

class TweetsController < ApplicationController
  def retweet
    original_tweet = Tweet.find(params[:id])

    @retweet = Tweet.new(
      user_id: current_user.id,
      content: original_tweet.content
    )

    if @retweet.save
      redirect_to tweet_path, alert: 'Retweeted!'
    else
      redirect_to root_path, alert: 'Can not retweet'
    end
  end
end

retweet_params 引发错误的原因是您的 link link_to 'Retweet', retweet_tweet_path(@tweet.id), method: :post 不像新表单或编辑表单那样包含参数。相反,您应该创建一条新推文,引用您要转发的推文。

before_action :set_tweet, only: %i[show edit update destroy retweet]

def retweet
  retweet = @tweet.retweets.build(user: current_user)
  if retweet.save
    redirect_to retweet, notice: 'Retweeted!'
  else
    redirect_to root_path, alert: 'Can not retweet'
  end
end

private

def set_tweet
  @tweet = Tweet.find(params[:id])
end

以上应该会自动link 将新推文发送给“父级”。如果由于某种原因这不起作用,您可以通过将上面的内容更改为:

来手动设置它
retrweet = Tweet.new(retweet_id: @tweet.id, user: current_user)

上述方法不保存任何内容,因为这是转推。

如果您不想允许同一用户多次转发同一条推文,请确保您设置了适当的约束和验证。

# migration
add_index :tweets, %i[user_id retweet_id], unique: true

# model
validates :retweet_id, uniqueness: { scope: :user_id }

我们如何访问转推的内容?答案是我们从父级或源中获取内容(无论你想怎么称呼它)。

目前没有允许您访问父推文或源推文的关联。您目前已经拥有:

has_many :retweets, class_name: 'Tweet', foreign_key: 'retweet_id'

为了轻松访问源内容,让我们先添加一个额外的关联。

belongs_to :source_tweet, optional: true, inverse_of: :retweets, class_name: 'Tweet', foreign_key: 'retweet_id'
has_many :retweets, inverse_of: :source_tweet, class_name: 'Tweet', foreign_key: 'retweet_id'

通过设置上述关联,我们可以覆盖 Tweet 模型的 content getter 和 setter。

def content
  if source_tweet
    source_tweet.content
  else
    super
  end
end

def content=(content)
  if source_tweet
    raise 'retweets cannot have content'
  else
    super
  end
end

# depending on preference the setter could also be written as validation
validates :content, absence: true, if: :source_tweet

请注意,上述在谈论查询速度时效率不高,但这是最简单最清晰的解决方案。解决 parent/child 查询非常困难,如果速度成为问题,它应该有自己的问题。

如果您想知道我为什么设置 inverse_of 选项。我建议您查看 Active Record Associations - 3.5 Bi-directional Associations.

部分