我成功完成了Michael Hartl的Rails教程,你正在构建像twitter这样的应用程序。现在我想添加一些其他东西,并试图在微博中添加类似功能。
作为基础,我采用了粉丝之间的关系。
因此用户has_many(微博)'喜欢'和一个微博有很多喜欢的' (用户)。
我一直收到错误
ActionView::Template::Error: undefined method 'likers' for nil:NilClass
在Microposts界面测试和用户配置文件测试中。
这是我的代码:
routes.rb中:
Rails.application.routes.draw do
root 'static_pages#home'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
get 'login' => 'sessions#new'
post 'login' => 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users do
member do
get :following, :followers, :liked
end
end
resources :microposts do
member do
get :likers
end
end
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :microposts, only: [:create, :destroy]
resources :relationships, only: [:create, :destroy]
end
应用程序/模型/ like.rb:
class Like < ActiveRecord::Base
belongs_to :liker, class_name: "User"
belongs_to :micropost, class_name: "Micropost"
validates :liker_id, presence: true
validates :micropost_id, presence: true
end
应用程序/模型/ micropost.rb:
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :passive_likes, class_name: "Like",
foreign_key: "micropost_id",
dependent: :destroy
has_many :likers, through: :passive_likes, source: :micropost
default_scope -> { order(created_at: :desc) }
mount_uploader :picture, PictureUploader
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
validate :picture_size
private
# Validates the size of an uploaded picture.
def picture_size
if picture.size > 5.megabytes
errors.add(:picture, "maximal 5MB")
end
end
end
应用程序/模型/ user.rb:
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :active_likes, class_name: "Like",
foreign_key: "liker_id",
dependent: :destroy
has_many :liked, through: :active_likes, source: :micropost
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
mount_uploader :avatar, AvatarUploader
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
# Returns the hash digest of the given string.
def self.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def self.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
# Defines a proto-feed.
# See "Following users" for the full implementation.
# Returns a user's status feed.
def feed
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
Micropost.where("user_id IN (#{following_ids})
OR user_id = :user_id", user_id: id)
end
# Follows a user.
def follow(other_user)
active_relationships.create(followed_id: other_user.id)
end
# Unfollows a user.
def unfollow(other_user)
active_relationships.find_by(followed_id: other_user.id).destroy
end
# Returns true if the current user is following the other user.
def following?(other_user)
following.include?(other_user)
end
# Likes a micropost
def like(any_post)
active_like.create(micropost_id: any_post.id)
end
# Unlikes a micropost
def unlike(any_post)
active_like.find_by(micropost_id: any_post.id).destroy
end
# Returns true if the current user is liking the micropost
def liked?(any_post)
liked.include?(any_post)
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
应用程序/视图/用户/ show.html.erb:
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
</aside>
<div class="col-md-8">
<%= render 'follow_form' if logged_in? %>
<% if @user.microposts.any? %>
<h3>Posts (<%= @user.microposts.count %>)</h3>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
应用程序/视图/微柱/ _micropost.html.erb:
<li id="micropost-<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
<span class="content">
<%= micropost.content %>
<%= image_tag micropost.picture.url if micropost.picture? %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
<%= render 'shared/morestats' %>
<%= render 'microposts/like_form' if logged_in? %>
<% if current_user?(micropost.user) %>
<%= link_to "löschen", micropost, method: :delete,
data: { confirm: "Diesen Post wirklich löschen?" } %>
<% end %>
</span>
</li>
应用程序/视图/微柱/ _like_form.html.erb:
<% unless current_user?(@user) %>
<div id="like_form">
<% if @micropost.liked?(@user) %>
<%= render 'unlike' %>
<% else %>
<%= render 'like' %>
<% end %>
</div>
<% end %>
应用程序/视图/微柱/ _like.html.erb:
<%= form_for(current_user.active_likes.build) do |f| %>
<div><%= hidden_field_tag :micropost_id, @user.id %></div>
<%= f.submit "Like", class: "btn btn-primary" %>
<% end %>
应用程序/视图/微柱/ _unlike.html.erb:
<%= form_for(current_user.active_likes.build) do |f| %>
<div><%= hidden_field_tag :micropost_id, @user.id %></div>
<%= f.submit "Like", class: "btn btn-primary" %>
<% end %>
如果有任何代码丢失,请告诉我。提前谢谢!
这里有完整的错误:
ERROR["test_micropost_interface", MicropostsInterfaceTest, 4.72409967]
test_micropost_interface#MicropostsInterfaceTest (4.72s)
ActionView::Template::Error: ActionView::Template::Error: undefined method `likers' for nil:NilClass
app/views/shared/_morestats.html.erb:4:in `_app_views_shared__morestats_html_erb___939926434685355917_93681000'
app/views/microposts/_micropost.html.erb:10:in `_app_views_microposts__micropost_html_erb___1029196025817541101_93766560'
app/views/users/show.html.erb:19:in `_app_views_users_show_html_erb___2389533090581269630_85562520'
test/integration/microposts_interface_test.rb:33:in `block in <class:MicropostsInterfaceTest>'
app/views/shared/_morestats.html.erb:4:in `_app_views_shared__morestats_html_erb___939926434685355917_93681000'
app/views/microposts/_micropost.html.erb:10:in `_app_views_microposts__micropost_html_erb___1029196025817541101_93766560'
app/views/users/show.html.erb:19:in `_app_views_users_show_html_erb___2389533090581269630_85562520'
test/integration/microposts_interface_test.rb:33:in `block in <class:MicropostsInterfaceTest>'
test / integration / microposts_interface_test.rb的代码:
require 'test_helper'
class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:peter)
end
test "micropost interface" do
log_in_as(@user)
get root_path
assert_select 'div.pagination'
# Invalid submission
assert_no_difference 'Micropost.count' do
post microposts_path, micropost: { content: "" }
end
assert_select 'div#error_explanation'
# Valid submission
content = "This micropost really ties the room together"
assert_difference 'Micropost.count', 1 do
post microposts_path, micropost: { content: content }
end
assert_redirected_to root_url
follow_redirect!
assert_match content, response.body
# Delete a post.
assert_select 'a', text: 'löschen'
first_micropost = @user.microposts.paginate(page: 1).first
assert_difference 'Micropost.count', -1 do
delete micropost_path(first_micropost)
end
# Visit a different user.
get user_path(users(:archer))
assert_select 'a', text: 'löschen', count: 0
end
end
test / integration / users_profile_test.rb的代码:
require 'test_helper'
class UsersProfileTest < ActionDispatch::IntegrationTest
include ApplicationHelper
def setup
@user = users(:peter)
end
test "profile display" do
get user_path(@user)
assert_template 'users/show'
assert_select 'title', full_title(@user.name)
assert_select 'h1', text: @user.name
assert_select 'h1>img.gravatar'
assert_match @user.microposts.count.to_s, response.body
assert_select 'div.pagination'
@user.microposts.paginate(page: 1).each do |micropost|
assert_match micropost.content, response.body
end
end
end
shared / _morestats.html.erb的代码:
<% @micropost %>
<div class="morestats">
<strong id="likers" class="morestat">
<%= @micropost.likers.count %>
</strong>
likers
</div>
答案 0 :(得分:0)
问题在于你写了@micropost引用了一个对所有视图都可见的变量(你从未分配过的@micropost对象)。如果你在没有@的情况下编写微博,你指的是一个分配了:locals =&gt;的局部变量。 {:micropost =&gt;微博}。
在共享/ morestats第4行,你正在做@ micropost.likers但是如果你注意,你没有将任何微博传递给部分(参见微博,第10行):
<%= render 'shared/morestats' %>
您必须将其更改为以下内容:
<%= render :partial => 'shared/morestats', :locals => { :micropost => micropost } %>
并且从morestats的微博中移除'@'。就像这样:
<% micropost %>
<div class="morestats">
<strong id="likers" class="morestat">
<%= micropost.likers.count %>
</strong>
likers
</div>
您在此处遇到同样的问题:
<%= render 'microposts/like_form' %>
您必须将其更改为以下内容:
<%= render :partial => 'microposts/like_form', :locals => { :micropost => micropost } %>
并喜欢这个:
<% unless current_user?(@user) %>
<div id="like_form">
<% if @user.liked?(micropost) %>
<%= render 'unlike' %>
<% else %>
<%= render 'like' %>
<% end %>
</div>
<% end %>
另一种解决方案,如果您不想更改@,您可以像这样修改_micropost部分:
<% @micropost = micropost %>
<%= render 'shared/morestats' %>
但这不如之前的解决方案那么优雅。