Rails user to user messages -
i'm new rails please detailed in responses. i'm building web app uses devise authentication. part i'm stuck on right user user messaging system. idea user logs app , can visit user b's profile, , on user b's profile can click on link allows user compose message user b. user b can log app , visit inbox user a's message found.
i believe i'm having trouble defining sender , recipient roles here, right i'm trying display form users compose message in. can see i'm doing wrong here? following error. i've read thing add user_id field table, i'm hoping link messages using sender_id , recipient_id, both equal user_id (e.g. user 1[sender] sends message user 2 [recipient]):
unknown attribute: user_id
def new @message = current_user.messages.new recipient_id: params[:sender_id] end
additionally, rails experts or has done similar this, can advise whether or not i'm going in right direction, or offer guidance? i'm sort of coding blind here , trying make go along. guidance hugely appreciated , save me lot of time i'm sure. code below:
users migration
class devisecreateusers < activerecord::migration def change create_table(:users) |t| t.string :first_name t.string :last_name t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" t.string :reset_password_token t.datetime :reset_password_sent_at t.datetime :remember_created_at t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip t.timestamps end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true end end messages migration
class createmessages < activerecord::migration def change create_table :messages |t| t.string :content t.integer :sender_id t.integer :recipient_id t.timestamps end end end schema.rb
activerecord::schema.define(version: 20140909174718) create_table "messages", force: true |t| t.string "content" t.integer "sender_id" t.integer "recipient_id" t.datetime "created_at" t.datetime "updated_at" end create_table "users", force: true |t| t.string "first_name" t.string "last_name" t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" t.datetime "created_at" t.datetime "updated_at" t.string "current_industry" t.integer "years_in_current_industry" t.string "hobbies" end add_index "users", ["email"], name: "index_users_on_email", unique: true add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end routes.rb
catalyst::application.routes.draw devise_for :users, :controllers => { :registrations => "registrations" } devise_scope :user 'register', to: 'devise/registrations#new' 'login', to: 'devise/sessions#new', as: :login 'logout', to: 'devise/sessions#destroy', as: :logout end resources :users member 'edit_profile' end resources :messages, only: [:new, :create] end resources :messages, only: [:index, :show, :destroy] root to: "home#index" match '/about', to: 'static_pages#about', via: 'get' match '/contact', to: 'static_pages#contact', via: 'get' match '/help', to: 'static_pages#help', via: 'get' match '/legal', to: 'static_pages#legal', via: 'get' end users_controller
class userscontroller < applicationcontroller before_filter :authenticate_user! def index @users = user.all end def show @user = user.find(params[:id]) end def new end def create end def edit end def update @user = user.find(params[:id]) @user.update!(user_params) redirect_to @user end def destroy end def edit_profile @user = user.find(params[:id]) end def user_params params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :current_industry, :years_in_current_industry, :hobbies) end def sender @user = user.find(params[:id]) end def recipient @user = user.find(params[:id]) end end messages_controller
class messagescontroller < applicationcontroller before_action :set_recipient def new @message = message.new @recipient = user.find(params[:user_id]) end def create @message = message.new message_params if @message.save flash[:success] = "your message has been sent!" redirect_to user_messages_path else flash[:failure] = "please try again." redirect_to users_path end end private def message_params params.require(:message).permit(:content, :sender_id, :recipient_id) end end user.rb
class user < activerecord::base has_many :from_messages, class_name: 'message', :foreign_key => "sender_id" has_many :to_messages, class_name: 'message', :foreign_key => "recipient_id" devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable attr_accessible :first_name, :last_name, :email, :password, :password_confirmation, :remember_me, :current_industry, :years_in_current_industry, :hobbies end message.rb
class message < activerecord::base belongs_to :sender, class_name: "user" belongs_to :recipient, class_name: "user" validates :content, presence: true, length: { maximum: 500 } validates :sender_id, presence: true validates :recipient_id, presence: true end messages/index.html.erb
<h2>inbox</h2> messages/new.html.erb
<h1>create message</h1> <%= form_for [@recipient, @message] |f| %> <%= f.hidden_field :recipient_id, value: @recipient.id %> <%= f.label "enter message below" %><br /> <%= f.text_area :content %> <%= f.submit "send" %> <% end %> rake routes
user_messages post /users/:user_id/messages(.:format) messages#create new_user_message /users/:user_id/messages/new(.:format) messages#new users /users(.:format) users#index post /users(.:format) users#create new_user /users/new(.:format) users#new edit_user /users/:id/edit(.:format) users#edit user /users/:id(.:format) users#show patch /users/:id(.:format) users#update put /users/:id(.:format) users#update delete /users/:id(.:format) users#destroy messages /messages(.:format) messages#index message /messages/:id(.:format) messages#show delete /messages/:id(.:format) messages#destroy
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, -> { read: false } end this should give ability create messages "belong" user (ie recipient), , can associate "sender" profile messages.
--
controllers
this give ability call 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 :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 give ability use following links:
#app/views/users/show.html.erb (user send message to) <%= link_to "send message", user_messages_path(@user.id) %> #app/views/messages/new.html.erb <%= form_for [@recipient, @user] |f| %> <%= f.text_field :content %> <%= f.submit %> <% end %> #app/views/messages/index.html.erb <h2>inbox</h2> <% @messages.each |message| %> <%= message.content %> <% end %> --
fix
i've read thing add user_id field table, i'm hoping link messages using sender_id , recipient_id, both equal user_id (e.g. user 1[sender] sends message user 2 [recipient])
you don't need add user_id table. user_id merely foreign_key, you've overridden in models.
all need set recipient_id , sender_id, we're doing in create method:
def create @message = current_user.message.new message_params @message.recipient_id = @recipient.id @message.save end you've done clever things here.
firstly, have implicitly set sender_id foreign key calling current_user.messages. if had called message.new, have been different story (having set sender_id)
secondly, because you're using nested routes, you'll able use @recipient variable you've set in before_action method give id recipient_id.
this should work you. won't need use inverse_of unless trying access "parent" model data in child / nested model.
recommendations
what you're doing valid
the core trick make sure message model separate & independent user. achieved setup, allowing create various objects require.
the other aspect need consider how you're going ensure you're able provide users ability have "threaded" messages. you'll achieve using 1 of hierarchy gems, either ancestry or closure_tree
adding functionality little more in-depth. can provide information if require (just leave comment)
threading
the hierarchy gems relatively simple use.
the trick "treading" messages use 1 of these gems (either ancestry or closure_tree), provide "methods" can call on items. work creating several columns in database, populating them save / create objects desire
the "threading" issue big one, without "hierarchy" gems, won't able call "child" objects of record want, preventing threading occurring. here's good railscast on how achieve it:

the trick use called "recursion"
recursion create "indefinite" loop, far how "recursive" data is. eg if have object children, you'll have cycle through children, , children of children, recursively until reach point of showing data:
recursion process of repeating items in self-similar way. instance, when surfaces of 2 mirrors parallel each other, nested images occur form of infinite recursion.
as such, here's how it:
- make sure save objects correct parents
- to display "threaded" conversation, loop through parents
- use recursion loop through children
we use ancestry gem, stores hierarchy differently closure_tree gem we've since discovered (intend use closure tree gem soon).
you firstly have therefore save hierarchy yourself:

this allow save various "parents" object. means when load object, , wish cycle through descendent, you'll able use ancestry object methods:

which means you'll able use following:
#app/views/comments/index.html.erb <%= render partial: "comments", locals: { collection: @comments } %> #app/comments/_comments.html.erb <% collection.arrange.each |comment, sub_item| %> <%= link_to comment.title, comment_path(comment) %> <% if category.has_children? %> <%= render partial: "category", locals: { collection: category.children } %> <% end %> <% end %>
Comments
Post a Comment