I want show the user email as author of comment, but I see this error "undefined method `email' for nil:NilClass"
comment.rb
class Comment < ActiveRecord::Base
belongs_to :hotel
belongs_to :user
end
user.rb
class User < ActiveRecord::Base
has_many :hotels
has_many :comments
end
hotel.rb
class Hotel < ActiveRecord::Base
belongs_to :user
belongs_to :address
has_many :comments
mount_uploader :avatar, AvatarUploader
accepts_nested_attributes_for :address
end
comments_controller.rb
def create
#hotel = Hotel.find(params[:hotel_id])
#comment = #hotel.comments.new(comment_params)
#comment.user_id = current_user.id
#comment.save
redirect_to #hotel
end
private
def comment_params
params.require(:comment).permit(:user_id, :body, :hotel_id)
end
_comments.html.haml
= div_for comment do
%p
%strong
Posted #{time_ago_in_words(comment.created_at)} ago
%br/
= h comment.user.email
%br
= comment.body
Method
The error that you're calling a method which doesn't exist.
The problem is you're calling a method on an associated object which doesn't exist. You probably don't have any user associated to the comment - thus preventing you from being able to call the email method.
Firstly, you need to make sure you have the correct association. Here's how to do that:
$ rails c
$ comment = Comment.find([id])
$ comment.update(user_id: [your_user_id])
$ exit
This will allow you to associate the comment to a particular user, giving you the ability to call the associated method.
--
Controller
When you save your comment in your controller, you need to assign your user to it. We do this using the strong_params functionality, as its the DRYest way we've found:
#app/controllers/comments_controller.rb
Class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
end
private
def comment_params
params.require(:comment).permit(:your, :comment: attributes).merge(user_id: current_user.id)
end
end
This will allow you to associate the user at save time, giving you the ability to call the methods you need next time you call the record!
Delegate
You'll also benefit from using the delegate method like this:
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :hotel
delegate :email, to: :user, prefix: true #-> allows you to call `#comment.user_email`
end
This will solve the law of Demeter issue (where you should aim to have one "point" in your calls")
Related
I am new in Rails and I want to create shopping History. I think my association doesn't work correctly or my function current order
My model
User
Order
Order_line_item
Order_item
Cart
So my Db Looks like this
When I click "Buy" button in cart Oder_items create in Order_line_items. For example you can order 1 item or several items. The quantity of you order save in Order Line Items. So the order_line_items save in Order. It's order history. This is how My logic work. I think so. I can see any order by id. Like this
Association
class User < ApplicationRecord
has_many :cart_items
has_many :order_items
has_many :order_line_items, through: :order_items
has_one :cart, dependent: :destroy
has_one :order, dependent: :destroy
end
class Order < ApplicationRecord
has_many :order_line_items
has_many :order_items
belongs_to :user
end
class OrderLineItem < ApplicationRecord
has_many :order_items
belongs_to :order
has_one :user, through: :order
end
class OrderItem < ApplicationRecord
belongs_to :order_line_items
belongs_to :order
belongs_to :product
# has_one :user, through: :order_line_items, :source => :order
end
Application Helper
module ApplicationHelper
def current_order_line(order_line)
if OrderItem.find_by(order_line_id: order_line[:id]).present?
OrderItem.find_by(order_line_id: order_line[:id])
else
current_order.order_line_items || current_order.build_order_line_items
end
end
#I think this code doesn't work property
def current_order
if session[:order_id].present?
Order.find(session[:order_id])
else
current_user.order || current_user.build_order
end
end
def current_cart
if session[:cart_id].present?
Cart.find(session[:cart_id])
else
current_user.cart || current_user.build_cart
end
end
end
My Controllers
class OrdersController < ApplicationController
def show
#order_line_items = current_order.order_line_items
end
end
class OrderItemsController < ApplicationController
def create
#order_line_items = current_order_line_items
#order_item = #order_line_items.order_items.new(order_params)
#order_line_items.save
redirect_to my_orders_path
OrderMailer.order_confirmation(current_user, #order_item).deliver_now
flash[:success] = "Order has been confirmed"
session[:order_id] = #order.id
session[:user_id] = current_user.id
def order_params
params.require(:order_item).permit(:product_id)
end
end
#There I need also call order_line_item to create order. I don't know how
class OrderLineItemsController < ApplicationController
def show
#order_items = current_order.order_items
end
class CartsController < ApplicationController
def show
#cart_items = current_cart.cart_items
#order_line_item = current_order.order_line_items.new
#order_item = current_order_line(#order_line_item.id).order_items #I got error here
end
Error message:
NoMethodError in CartsController#show
undefined method `[]' for nil:NilClass
I think I made a mistake with my association and my function current_order_line doesn't work property. I will be pressure If you can help me!
Maybe I think It can be work
module ApplicationHelper
def current_order_line
if session[:order_line_id].present?
OrderLineItem.find(session[:order_line_id])
else
current_order.order_line_items || current_order.build_order_line_items
end
end
class CartsController < ApplicationController
def show
#cart_items = current_cart.cart_items
#order_line_item = current_order.order_line_items.new
#order_item = current_order_line.order_items.new
end
So then I got this error message
NoMethodError in CartsController#show
undefined method `order_items' for #<ActiveRecord::Associations::CollectionProxy []>
I believe that your issue might be here.
def current_order_line
if session[:order_line_id].present?
OrderLineItem.find(session[:order_line_id])
else
current_order.order_line_items || current_order.build_order_line_items
end
end
If I look at the if block OrderLineItem.find(session[:order_line_id]), you are returning a OrderLineItem record.
However, the else block current_order.order_line_items || current_order.build_order_line_items does not return a OrderLineItem record. Instead, you are returning a relationship and not a single record.
When you do current_order.order_line_items, you are returning an ActiveRecord collection (not a single record). And ActiveRecord does not have a order_items method; that method is defined on OrderItem
If you want to keep the current design, what you need to do is return an OrderLineItem from current_order_line. I think the following should work (syntactically, but not sure if that makes sense in terms of the business logic):
def current_order_line
if session[:order_line_id].present?
OrderLineItem.find(session[:order_line_id])
else
if !current_order.order_line_items
current_order.build_order_line_items
end
current_order.order_line_items.first
end
end
I'm still somewhat new to Ruby and am having trouble displaying data on the show page from another class. I have two classes, Company and Job. On the Job show page I would like to display the Company's name, website and description from the Company form fields that created/posted the job when a job applicant views the respective job.
Was receiving an error when tinkering with the Job show controller action. Not entirely sure if the company is not being assigned an id when being created or if there's an issue with the show action login in the controller or a model association error on my end. Any help and explanation to resolve this issue is greatly appreciated.
Screenshot for Error Received on Job Show Page
Models
class Company < ApplicationRecord
has_many :jobs
has_many :job_applications, through: :jobs
class Job < ApplicationRecord
belongs_to :user
belongs_to :company, optional: true
has_many :job_applications, dependent: :destroy
class JobApplication < ApplicationRecord
belongs_to :user
belongs_to :job
Controllers
class CompaniesController < ApplicationController
private
# Use callbacks to share common setup or constraints between actions.
def set_company
#company = Company.find(params[:id])
# #company = self.create_company
end
# Only allow a list of trusted parameters through.
def company_params
params.require(:company).permit(:name, :website, :about, :user_id, :avatar)
end
class JobsController < ApplicationController
# GET /jobs/1 or /jobs/1.json
def show
#company = Company.find(params[:user_id])
# #company = Company.all
# #job = Job.find(params[:id])
end
Routes
resources :companies
resources :jobs
resources :jobs do
resources :job_applications
end
Job Show Page
<%= #company.name %>
<%= #company.website %>
<%= #company.about %>
I believe the problem lies in your show method in the JobsController.
It should look something like this:
class JobsController < ApplicationController
# GET /jobs/1 or /jobs/1.json
def show
#job = Job.find(params[:id])
#company = #job.company
end
This might throw some errors since you have optional: true in your relation. Also, I didn't care of n+1 queries since it's just a record, but this could be improved to be only 1 SQL query to the database.
Trying to figure out a better way of assigning a review it's associated models.
I have the following classes:
class User < ActiveRecord::Base
has_many :reviews, dependent: :destroy
end
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :restaurant
end
class Restaurant < ActiveRecord::Base
has_many :reviews, dependent: :destroy
end
Pretty straightforward stuff. A review must have a restaurant and a user. My create action looks like this:
def create
#restaurant = Restaurant.find(params[:restaurant_id])
#review = #restaurant.reviews.build(review_params)
#review.user = current_user
if #review.save
redirect_to #restaurant
else
render 'new'
end
end
private
def review_params
params.require(:review).permit(:content)
end
Currently I build the review for the restaurant and then I assign the review's user to the current user.
This all works fine but is there a cleaner way to build the associations?
Is there a way to add additional arguments to the build method alongside the strong params?
I looked at accepts_nested_attributes_for but I couldn't get it to work.
Thanks!
You can use merge in the review_params like below
def review_params
params.require(:review).permit(:content).merge(user_id: current_user.id)
end
so that you can erase this line #review.user = current_user in the create method
In your form, you can put a hidden field with the user_id that you want to assign:
<%= f.hidden_field :user_id, value: #user.id %>
Then, add it to your review_params:
params.require(:review).permit(:content, :user_id)
I'm setting up an internal messaging system in my rails app and I'm having trouble getting the message to actually send to another user.
class User < ActiveRecord::Base
# messages and conversations
has_many :user_conversations
has_many :conversations, through: :user_conversations
has_many :messages
class UserConversation < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
before_create :create_user_conversations
accepts_nested_attributes_for :conversation
delegate :subject, to: :conversation
delegate :users, to: :conversation
attr_accessor :to
private
def create_user_conversations
to.each do |recip|
recipient = User.find(recip)
UserConversation.create(user_id: recip, conversation_id: 1)
end
end
end
class Conversation < ActiveRecord::Base
has_many :user_conversations
has_many :users, through: :user_conversations
has_many :messages
accepts_nested_attributes_for :messages
class Message < ActiveRecord::Base
belongs_to :user_conversation
belongs_to :user
And here is my user_conversation_controller:
class UserConversationsController < ApplicationController
def new
#user = User.find(params[:user_id])
#conversation = #user.user_conversations.build
#conversation.build_conversation.messages.build
end
def create
#conversation = UserConversation.new(conversation_params)
#conversation.user = current_user
#conversation.conversation.messages.first.user = current_user
if #conversation.save
redirect_to user_conversation_path(current_user, #conversation)
else
flash[:error] = "There was an error"
render 'new'
end
end
private
def conversation_params
params.require(:user_conversation).permit(:to => [],
conversation_attributes: [:subject,
messages_attributes: [:body]])
end
The error comes in the create_user_conversations method in the UserConversation model. When I try to run
to.each do |recip|
I get an "undefined method 'each' for nil:NilClass" error. However, the "to" array has a value in it, in this case the parameters looked like this:
{"utf8"=>"✓",
"user_conversation"=>{"to"=>["2"],
"conversation_attributes"=>{"subject"=>"Hey",
"messages_attributes"=>{"0"=>{"body"=>"hey"}}}},
"commit"=>"Create User conversation",
"user_id"=>"1"}
Any ideas on why that array isn't getting passed in correctly? Thanks.
You define to as an attr_accessor, which will create get/set methods for an instance variable #to. You're using to as a local variable in your private method create_user_conversations though. This explains the nil:NilClass error.
Try changing the local variable to be an instance variable instead.
I solved my problem by going ahead and adding a recipients_id column to my user_conversations table, then in my UserConversations controller I was able to do
def create
#conversation = UserConversation.new(user_conversation_params)
#conversation.user = current_user
#conversation.conversation.messages.first.user_id = current_user.id
if #conversation.save
UserConversation.recipient_id = #conversation.recipient_id
redirect_to user_conversation_path(current_user, #conversation)
create_user_conversations
else
flash[:error] = "There was an error"
render 'new'
end
end
With the private method create_user_conversations also in my UserConversations controller:
def create_user_conversations
UserConversation.recipient_id.each do |recip|
recipient = User.find(recip)
UserConversation.create(user: recipient, conversation: #conversation.conversation)
end
end
I doubt this is the most elegant way to do this, but it at least gets the job done.
I'm creating a simple newsfeed in rails. The aim is for it to return all the posts from the groups the user is following. I am using socialization for my follow functionality.
The exact error is:
NoMethodError (undefined method `followees' for false:FalseClass)
Here are my basic models not including like and follow as they're empty:
User:
class User < ActiveRecord::Base
authenticates_with_sorcery!
attr_accessible :username, :password, :email
has_many :groups
has_many :posts
acts_as_follower
acts_as_liker
before_create :generate_auth_token
def auth_token_expired?
auth_token_expires_at < Time.now
end
def generate_auth_token(expires = nil)
self.auth_token = SecureRandom.hex(20)
self.auth_token_expires_at = expires || 1.day.from_now
end
def regenerate_auth_token!(expires = nil)
Rails.logger.info "Regenerating user auth_token"
Rails.logger.info " Expiration: #{expires}" if expires
generate_auth_token(expires)
save!
end
end
Group:
class Group < ActiveRecord::Base
attr_accessible :description, :name, :user_id
has_many :posts
belongs_to :user
acts_as_followable
end
Post:
class Post < ActiveRecord::Base
attr_accessible :body, :user_id, :group_id
belongs_to :user
belongs_to :group
acts_as_likeable
end
I have setup a function named newsfeed in my post controller. The function grabs all the groups that a user is following and then grabs all the posts that have group_ids matching group_ids in the returned groups array. But I keep getting unidentified method followees(socialization provides this). Yet it appears to work when using single users and posts in irb.
def newsfeed
#groups = current_user.followees(Group)
#posts = Post.where(:group_id => #groups)
respond_to do |format|
format.html # show.html.erb
format.json { render json: #posts }
end
end
Thanks for any help.
Apparently, your current_user method returns false, instead of a user. Check what's returned from that method, as find out why you get the error...
Your current_user return false instead of instance of User. You may see it from error text.