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.
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 have a User model, a group model (Cliq), and a group_membership model (Cliq_Membership). Everything seems to be working fine so far. I currently have it so that when a User creates a group they "own" it and when an "owner" leaves the group (destroys their group membership) the entire group is destroyed. A group has one owner and many members. I want to make it so that a User has to request to be a "member". I want the "owner" to be the only one to see the requests and accept/deny the requests.
For Clarity:
I want users to have to request to be group members
I want Cliqs to be able to request Users to be members
Only the owner should be able to see/accept/deny friend requests
I want the relationship to be "two-way/self-referential"; that is, I want the User/Member to be shown as being included in the group and the group as having another member
Cliqs = Groups
How do you accomplish this?
Here is my code so far:
Models:
class User < ActiveRecord::Base
has_many :uploads
has_one :owned_cliq, foreign_key: 'owner_id', class_name: 'Cliq', dependent: :destroy
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
end
class Cliq < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :cliq_memberships, dependent: :destroy
has_many :members, through: :cliq_memberships, source: :user
end
Controllers:
class CliqMembershipsController < ApplicationController
def create
#Cliq or Cliq_ID?
#cliq = Cliq.find(params[:cliq])
#cliq_membership = current_user.cliq_memberships.build(cliq: #cliq)
#cliq.members << current_user
if #cliq_membership.save
flash[:notice] = "Joined #{#cliq.name}"
else
flash[:notice] = "Not able to join Cliq."
end
redirect_to cliq_url
end
def destroy
#cliq_membership = current_user.cliq_memberships.find(params[:id])
#cliq = #cliq_membership.cliq
if #cliq.owner == current_user
#cliq.destroy
flash[:notice] = "Cliq has been deleted."
redirect_to current_user
else
#cliq_membership.destroy
flash[:notice] = "You left the Cliq."
redirect_to current_user
end
end
end
class CliqsController < ApplicationController
def show
#cliq = Cliq.find(params[:id])
end
def new
#cliq = Cliq.new(params[:id])
end
def create
#cliq = current_user.build_owned_cliq(cliq_params)
#cliq.members << current_user
if #cliq.save
redirect_to current_user
else
redirect_to new_cliq_path
end
end
def destroy
##cliq = current_user.owned_cliq.find(params[:id])
#lash[:alert] = "Are you sure you want to delete your Cliq? Your Cliq and all of its associations will be permanently deleted."
##cliq.destroy
#if #cliq.destroy
#redirect_to current_user
#flash[:notice] = "You deleted the Cliq."
#else
#redirect_to current_user
#set up error handler
#flash[:notice] = "Failed to delete Cliq."
#end
end
def cliq_params
params.require(:cliq).permit(:name, :cliq_id)
end
end
You can create another model and controller for handling user requests
create request.rb model
class Request < ActiveRecord::Base
belongs_to :user
belongs_to :cliq
end
create requests_controller.rb.
class RequestsController < ApplicationController
before_action :set_group
before_action :auth_group_owner
before_action :find_request, except: [:index, :create]
def index
end
def create
#grp.requests.where(user_id: current_user.id).first_or_create
# redirect the user
end
def approv
# add the user to the group
#request.destroy
# redirect
end
def destroy
#delete the request
end
private
def set_group
#find group #grp
end
def auth_group_owner
if current_user != #grp.owner
redirect
end
end
def find_request
#find request
end
end
your routes.rb
resources :groups do
resources :requests, only: [:index, :destroy] do
member do
get 'approv'
end
end
end
The following solution should work without creating a new model for requests. Adding a new boolean field to CliqMembership model to store whether a particular cliq_memberhip is confirmed or not is sufficient. (Let's call that field 'confirmed', for example)
class User < ActiveRecord::Base
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
has_many :confirmed_memberships, -> { confirmed }, class_name: "CliqMembership"
has_many :confirmed_cliqs, through: :confirmed_memberships, source: :cliq
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
scope :confirmed, -> { where(confirmed: true) }
end
class Cliq < ActiveRecord::Base
has_many :cliq_memberships, dependent: :destroy
has_many :members, through: :cliq_memberships, source: :user
has_many :confirmed_memberships, { confirmed }, class_name: "CliqMembership"
has_many :confirmed_members, through: :confirmed_memberships, source: :user
end
With this, you can set the value of confirmed field to false by default when a new cliq_membership is created by a user. Until the owner update's that particular cliq_membership to change the value of confirmed to true.
Assuming user & cliq are instances of User model & Cliq model respectively, you can now use user.confirmed_cliqs and cliq.confirmed_members.
Edit:
In order to restrict the edit & update actions on cliq_membership to only the cliq owner, you can use a before filter.
class CliqMembershipsController < ApplicationController
before_action :cliq_owner, only: [:edit, :update]
def edit
#cliq_membership = CliqMembership.find(params[:id])
end
def update
#cliq_membership = CliqMembership.find(params[:id])
#cliq_membership.update_attributes(cliq_membership_params)
end
private
def cliq_membership_params
params.require(:cliq_membership).permit(:cliq_id, :user_id, :confirmed)
end
def cliq_owner
#cliq = CliqMembership.find(params[:id]).cliq
redirect_to root_url unless #cliq.owner == current_user
end
end
Hope it works for you.
In my messages controller I wish to build a message that belongs_to a #sender (class Character). However, messages belong_to not only Characters but also Conversations, and this is causing the problem. The build method of the message needs to be passed the Conversation id to which it belongs so the message can pass validations. My code successfully finds the #conversation via an sql search, but how do I correctly pass both the #conversation.id and the message_params to the #sender.messages.build as strong parameters?
messages_controller.rb
def create
#sender = Character.find_by(callsign: params[:callsign])
#recipient = Character.find_by(callsign: params[:recipient])
sender_id = #sender.id
recipient_id = #recipient.id
conversationID = Conversation.find_by_sql("
SELECT senderConversations.conversation_id FROM Chats AS senderConversations
INNER JOIN Chats AS recipientConversations
ON senderConversations.conversation_id=recipientConversations.conversation_id
WHERE senderConversations.character_id='#{sender_id}'
AND recipientConversations.character_id='#{recipient_id}'
GROUP BY senderConversations.conversation_id
HAVING count(distinct senderConversations.character_id) = 2
; ")
#conversation = Conversation.find_by(id: conversationID)
if(#conversation.nil?)
#conversation = #sender.conversations.create
#recipient.conversations << #conversation
end # It all works great up to here!
#message = #sender.messages.build(message_params, #conversation) # Can't get this working
if #message.save
#conversation.messages << #message
respond_to do |format|
format.html do
end
format.js do
end
end
else
redirect_to request.referrer || root_url
end
end
private
def message_params
params.require(:message).permit( :content, :picture )
end
character.rb
has_many :chats, foreign_key: "character_id",
dependent: :destroy
has_many :conversations, through: :chats, source: :conversation
has_many :messages
conversation.rb
has_many :messages
chat.rb
belongs_to :character
belongs_to :conversation
message.rb
belongs_to :character
belongs_to :conversation
validates :character_id, presence: true
validates :conversation_id, presence: true
To add conversation to the message, try
#message = #sender.messages.build(message_params.merge(:conversation => #conversation))
A couple suggestions -
First, move all the querying code to the Message model. Given the params you're setting, the sender and recipient, you should be able to find or create the conversation and attach it correctly. Reduce the complexity of the controller.
Also, try to move that sql into a rails query if you can and set a scope. Not sure I completely understand the schema, but something like...
class Conversation < ActiveRecord::Base
scope :between, (sender, recipient) -> { joins(:chats).where(:charcter_id => [sender, recipient]).group('chats.conversations_id').having("count(distinct senderConversations.character_id) = 2") }
Then you can look for the conversation on a before_save or before_validation callback, ensuring the conversation exists. In a scope, you'll be able to reuse the sql more easily to find the conversation in other situations.
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")
I have a Post which has many comments. I am trying to send a notification email to the owner of the post that someone has commented on their post but I am having a hard time trying to get the owner of the post to the mailer. the code if as follows
class CommentsController < ApplicationController
def create
#commentable = find_commentable
#comment = #commentable.comments.build(comment_params)
#comment.user_id = current_user.id
if #comment.save
flash[:notice] = "Successfully posted an offer."
PostMailer.comment_posted(----).deliver #this is the mail code
redirect_to #commentable
else
flash[:error] = "Error adding an offer."
end
end
end
below is the mailer code
class PostMailer < ActionMailer::Base
default from: "contact#example.com"
def comment_posted(user)
#user = user
mail to: user.first_name, subject: "You have a new Comment!"
end
end
below is the comment model
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, :polymorphic => true
has_ancestry
end
and the Post model
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments, :as => :commentable, dependent: :destroy
end
Use this:
PostMailer.comment_posted(#commentable.user).deliver
#commentable will give you corresponding Post record for the given comment. Post belongs_to a user, so you can access the poster using #commentable.user.
I noticed in the controller you are doing:
PostMailer.comment_posted
Shouldn't this be:
PostMailer.offer_posted(#comment.user).deliver
?