Rails user to user messages

一曲冷凌霜 提交于 2019-12-01 01:05:03

Models

#app/models/user.rb
class User < ActiveRecord::Base
   has_many :messages, class_name: "Message", foreign_key: "recipient_id"
   has_many :sent_messages, class_name: "Message", foreign_key: "sender_id"
end

#app/models/message.rb
class Message < ActiveRecord::Base
   belongs_to :recipient, class_name: "User", foreign_key: "recipient_id"
   belongs_to :sender, class_name: "User", foreign_key: "sender_id"
   scope :unread, -> { where read: false }
end

This should give you the ability to create messages which "belong" to a user (IE the recipient), and then you can associate a "sender" profile to those messages.

--

Controllers

This will give you the ability to call the following:

#app/controllers/messages_controller.rb
class MessagesController < ApplicationController
   before_action :set_recipient, only: [:new, :create]

   def new
      @message = current_user.sent_messages.new
   end

   def create
      @message = current_user.sent_messages.new message_params
      @message.recipient_id = @recipient.id
      @message.save
   end

   def index
      @messages = current_user.messages
   end

   def destroy
      @message = current_user.messages.destroy params[:id]
   end

   def show
      @message = current_user.messages.find params[:id]
   end

   private

   def message_params
      params.require(:message).permit(:content, :recipient_id, :sender_id)
   end

   def set_recipient
       @recipient = User.find params[:user_id]
   end
end

--

Routes

#config/routes.rb
devise_for :users, path: "", controllers: { :registrations => "registrations" }, path_names: {sign_up: "register", sign_in: "login", sign_out: "logout"}

resources :users do
   get :profile
   resources :messages, only: [:new, :create] #-> domain.com/users/:user_id/messages/new
end
resources :messages, only: [:index, :show, :destroy] #-> domain.com/messages/:id

--

Views

This will give you the ability to use the following links:

#app/views/users/show.html.erb (user to send message to)
<%= link_to "Send Message", user_messages_path(@user.id) %>

#app/views/messages/new.html.erb
<%= form_for [@recipient, @user] do |f| %>
     <%= f.text_field :content %>
     <%= f.submit %>
<% end %>

#app/views/messages/index.html.erb
<h2>Inbox</h2>
<% @messages.each do |message| %>
   <%= message.content %>
<% end %>

--

Fix

I've read that the thing to do is add the User_id field to the table, but I'm hoping to link this messages up using sender_id and recipient_id, which both equal user_id (e.g. User 1[sender] sends a message to User 2 [recipient])

You don't need to add user_id to your table. user_id is merely a foreign_key, which you've overridden in your models.

All you need to do is set the recipient_id and sender_id, which we're doing in the create method:

def create
   @message = current_user.message.new message_params
   @message.recipient_id = @recipient.id
   @message.save
end

You've done some very clever things here.

Firstly, you have implicitly set the sender_id foreign key by calling current_user.messages. If you had called Message.new, it would have been a completely different story (having to set sender_id)

Secondly, because you're using nested routes, you'll be able to use the @recipient variable you've set in the before_action method to give us the id for the recipient_id.

This should work for you. You won't need to use inverse_of unless you are trying to access "parent" model data in a child / nested model.


Recommendations

What you're doing is completely valid

The core trick is to make sure your Message model is completely separate & independent to your User. This is achieved with your setup, allowing you to create the various objects that you require.

The other aspect you need to consider is how you're going to ensure you're able to provide the users with the ability to have "threaded" messages. You'll achieve this using one of the hierarchy gems, either Ancestry or Closure_Tree

Adding this functionality will be a little more in-depth. I can provide information if you require (just leave a comment)


Threading

The hierarchy gems are actually relatively simple to use.

The trick to "treading" your messages is to use one of these gems (either Ancestry or Closure_Tree), as they provide you with "methods" which you can call on your items. They work by creating several columns in your database, populating them as you save / create the objects you desire

The "threading" issue is a big one, as without the "hierarchy" gems, you won't be able to call the "child" objects of the record you want, thus preventing the threading from occurring. Here's a good Railscast on how to achieve it:

The trick with this is to use something called "recursion"

Recursion is where you create an "indefinite" loop, so far as how "recursive" the data is. EG if you have an object with children, you'll have to cycle through the children, and then the children of those children, recursively until you reach the point of showing all the data:

Recursion is the process of repeating items in a self-similar way. For instance, when the surfaces of two mirrors are exactly parallel with each other, the nested images that occur are a form of infinite recursion.

As such, here's how you to it:

  1. Make sure you save your objects with the correct parents
  2. To display the "threaded" conversation, loop through those parents
  3. Use recursion to loop through their children

We use the ancestry gem, which stores the hierarchy slightly differently to the closure_tree gem we've since discovered (intend to use the closure tree gem soon).

You firstly have to therefore save any hierarchy yourself:

This will allow you to save the various "parents" for that object. This means that when you load the object, and wish to cycle through its descendent, you'll be able to use the Ancestry object methods:

Which means you'll be able to use the following:

#app/views/comments/index.html.erb
<%= render partial: "comments", locals: { collection: @comments } %>

#app/comments/_comments.html.erb
<% collection.arrange.each do |comment, sub_item| %>
    <%= link_to comment.title, comment_path(comment) %>

    <% if category.has_children? %>
        <%= render partial: "category", locals: { collection: category.children } %>
    <% end %>
<% end %>
roman-roman

To solve the error you have, try to set :inverse_of attribute of has_many and belongs_to statements in your model classes. You can end up having two has_many - one per each belongs_to reverse:

user.rb:
has_many :from_messages, :class_name => 'Message', :foreign_key => "sender_id",     :inverse_of => :sender
has_many :to_messages, :class_name => 'Message', :foreign_key => "to_id", :inverse_of => :recipient

message.rb:
belongs_to :sender, :class_name => 'User', :inverse_of => :from_messages
belongs_to :recipient, :class_name => 'User',:inverse_of => :to_messages

Overall I think your approach is a good starting point for a messaging system. You can try to post your code to https://codereview.stackexchange.com/ for a detailed review.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!