routes for user messages - ruby-on-rails

Im trying to build the functionality that gives users the ability to send each other messages. Ideally, a user can visit another user's profile and click on a link the redirects them to a form in which they will enter the message, and then click send and it will be delivered to the second user's inbox.
Separately, users should be able to visit an inbox page that displays all messages that have been sent to them.
I'm working on the first part now, and having trouble with the routes. Specifically, when I click on the send message link in a user's profile I'm redirected to the user's inbox page instead of being redirected to a form in which I can enter the context of my message and send. I'm not sure what I'm doing wrong here. Can anyone suggest how I can solve this routing issue?
routes.rb
devise_for :users, :controllers => { :registrations => "registrations" }
devise_scope :user do
get 'register', to: 'devise/registrations#new'
get 'login', to: 'devise/sessions#new', as: :login
get 'logout', to: 'devise/sessions#destroy', as: :logout
end
resources :users do
member do
get 'edit_profile'
get 'create_message'
end
end
resources :messages
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.rb
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 create_message
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.rb
class MessagesController < ApplicationController
def index
end
def new
end
def create
end
def destroy
end
end
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
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
#validates :first_name, presence: true, length: { maximum: 50 }
#validates :last_name, presence: true, length: { maximum: 50 }
end
messages.rb
class Messages < ActiveRecord::Base
belongs_to :sender
belongs_to :recipient
default_scope -> [ order('created_at DESC') ]
validates :sender_id, presence: true
validates :recipient_id, presence: true
end
index.html.erb
<h1>Inbox</h1>
creates_messages.html.erb
<h2>Create Message</h2>
<%= form_for #user do |f| %>
<%= f.label :content %><br />
<%= f.text_area :first_name, autofocus: true %>
<div><%= f.submit "Send" %></div>
<% end %>

Here's what I'd do:
#config/routes.rb
devise_for :users, path: "", :controllers => { :registrations => "registrations" }, path_names: {sign_up: "register", sign_in: "login", sign_out: "logout"}
resources :users do
resources :messages, only: [:new, :create]
end
resources :messages, only: [:index, :show, :destroy] #domain.com/messages -> inbox
#app/controllers/messages/new.html.erb
class MessagesController < ApplicationController
before_action :set_recipient
def new
#message = current_user.messages.new recipient_id: params[:user_id]
end
def create
#message = current_user.messages.new message_params
#message.sender_id = current_user.id
#message.recipient_id = #recipient.id
#message.save
end
private
def message_params
params.require(:message).permit(:title, :body, :sender_id, :recipient_id)
end
def set_recipient
#recipient = User.find params[:user_id] if params[:user_id].present?
end
end
#app/views/messages/new.html.erb
<%= form_for [#recipient, #message] do |f| %>
<%= f.text_field :title %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
Routes
The above code will give you the ability to use the following link:
#app/views/users/show.html.erb
<%= link_to "Send Message", user_message_path(#user) %>
You must remember that since Rails is object-orientated, everything you do (routes included) should revolve around objects. This is why you see the resources directive in your routing patterns -- it gives you the ability to define a series of CRUD based routes with various objects at their core.
I believe your problem is that you are not using the object orientated nature of Rails to proper extent. If you apply the routes as my example above, backed up with the relative methods, should give you the ability to create a very compelling flow.

Related

Rails: NoMethodError on creating has_one association with devise model

I am a complete beginner in Rails as such and I am trying to build a page to add extra profile data once the user logs in.
I am using Devise for authentication purposes and that works fine. I get this error and I have been stuck here.
undefined method `profiles'
Can you please help?
Codes
profiles_controller.rb
class ProfilesController < ApplicationController
before_action :authenticate_user!, only: [:new, :create, :show]
def new
#profile = current_user.profiles.build
end
def create
#profile = current_user.profiles.build(profile_params)
if #profile.save
format.html {redirect_to #profile, notice: 'Post was successfully created.'}
else
format.html {render 'new'}
end
end
def show
#profile = current_user.profiles
end
private
def profile_params
params.require(:profile).permit(:content)
end
end
The error seems to be coming from these lines in particular
def new
#profile = current_user.profiles.build
end
Other codes for reference:
/views/profiles/new.html.erb
<h1>Profiles#new</h1>
<p>Find me in app/views/profiles/new.html.erb</p>
<h3>Welcome <%= current_user.email %></h3>
<%= form_for(#profile) do |f| %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :text, autofocus: true %>
</div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<%end%>
routes.rb
Rails.application.routes.draw do
get 'profiles/new'
get 'profiles/create'
get 'profiles/show'
get 'profiles/update'
get 'pages/home'
get 'pages/dashboard'
devise_for :users, controllers: { registrations: "registrations" }
resources :profiles
root 'pages#home'
devise_scope :user do
get "user_root", to: "page#dashboard"
end
end
models/user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile, dependent: :destroy
end
models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
end
I just figured it out!
As the relationship is has_one, we should be using
def new
#profile = current_user.build_profile
end
instead of
def new
#profile = current_user.profiles.build
end
according to the documentation -
http://guides.rubyonrails.org/association_basics.html#has-one-association-reference
1) If your user must have many profiles. Set in your app/models/user.rb has_many :profiles
2) In your ProfilesController in new method instead of #profile = current_user.profiles use #profile = Profile.new
3) In your routes.rb delete
get 'profiles/new'
get 'profiles/create'
get 'profiles/show'
get 'profiles/update'
because you have already used resources :profiles
4) To stay with rules of DRY you can render form from a partial. Just add in views/profiles/_form.html.erb with the same content in your new.html.erb and after this you can delete everything im new.htm.erb and paste <%= render "form" %>. In future it will help you to render edit form if you want.
5) In your ProfilesController you can add method index with all profiles
def index
#profiles = Profile.all
end
You are trying to call an undefined relationship:
def new
#profile = current_user.profiles.build
end
has_one :profile
You should be calling:
def new
#profile = current_user.build_profile
end

What does .1 mean in `../users/1/profile.1`

/What does the .1 mean in ../users/1/profile.1? In editing an associated model in a one to one relationship, such as a user has one profile; it updates and redirected to ..users/user_id/profile.# instead of ../users/user_d/profile.
In the form_for, i used form_for [:user, #profile] to cover for the namespace through nested resources, but i don't understand why the .#. In an attempt to see if the link will cause my program to break, i clicked home (to take me back to my root page, basically reloading the profile as i had programmed for a logged in user), it reverts back to ../users/user_d/profile.
Using a debugging gem i get:
--- !ruby/hash:ActionController::Parameters
action: show
controller: profiles
user_id: '1'
format: '1'
What is format: '1'? Any explanation appreciated.
Adding my Code
USER.RB
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save {self.email = email.downcase }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[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, presence: true, length: { minimum: 6 }, allow_nil: true
has_one :profile, dependent: :destroy
accepts_nested_attributes_for :profile
end
PROFILE.RB
class Profile < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 50 }
validates :street, :city, :state, :zipcode, presence: true
belongs_to :user
end
Their controllers
USER CONTROLLER
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def new
#user = User.new
#profile = #user.build_profile
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Mini Olympics"
redirect_to user_profile_path(current_user, #profile)
else
render 'new'
end
end
def show
#user = User.find(params[:id])
end
def edit
# Commented out the code, as its redundant due to the line 'before_action :correct_user'
# #user = User.find(params[:id])
end
def update
# Commented out first line of the code, as its redundant due to the line 'before_action :correct_user'
# #user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "profile updated"
#redirect_to #user
redirect_to user_profile_path(current_user, #profile)
else
render 'edit'
end
end
def index
#users = User.paginate(page: params[:page], per_page: 15)
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:id, :email, :password, :password_confirmation, profile_attributes: [:name,
:street, :city, :state, :zipcode] )
end
# Before filters
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user) # '#user == current_user' = 'current_user?(#user)'
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
PROFILE CONTROLLER
class ProfilesController < ApplicationController
def edit
#profile = User.find(params[:user_id]).profile
end
def show
#profile = User.find(params[:user_id]).profile
end
def update
#profile = User.find(params[:user_id]).profile
if #profile.update_attributes(profile_params)
flash[:success] = "profile updated"
redirect_to user_profile_path(current_user, #profile)
else
render 'edit'
end
end
private
def profile_params
params.require(:profile).permit(:id, :name, :street, :city, :state, :zipcode)
end
end
Profile edit form
<% provide(:title, "Edit Profile") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for [:user, #profile] do |f| %>
<%= render 'fields', f: f %>
<%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>
</div>
</div>
APP/VIEWS/PROFILES/_FIELDS.HTML.ERB
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :street %>
<%= f.text_field :street, class: 'form-control' %>
<%= f.label :city %>
<%= f.text_field :city, class: 'form-control' %>
<%= f.label :state %>
<%= f.text_field :state, class: 'form-control' %>
<%= f.label :zipcode %>
<%= f.text_field :zipcode, class: 'form-control' %>
ROUTES FOLDER
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
resource :profile, only: [:show, :edit, :update ]
end
end
Usually the point following a dot at the end of a url is the format, for instance.
/users/12.html
/users/12.js
/users/12/profiles.xml
It looks like you've got a mal-formed url being generated somewhere which is passing the ID in as the format, as well as the id parameter.
That's the explanation, I'm not sure how to get rid of it without a little more information.
What does the users and profiles controllers look like in your routes file?
What does the link_to or url_for or *_url or *_path which generated this link look like?
Although my best guess is that you could just do form_for(#profile) to tidy this up. Then redirect in your create or update method to users_profiles_path(#user, #profile)
Update:
I put part of your routes file into a new rails app and got these routes
edit_user_profile GET /users/:user_id/profile/edit(.:format) profiles#edit
user_profile GET /users/:user_id/profile(.:format) profiles#show
PATCH /users/:user_id/profile(.:format) profiles#update
PUT /users/:user_id/profile(.:format) profiles#update
I missed the fact that you used resource instead of resources, so that each user has only one profile.
In the redirect, use user_profile_path(#user), you don't need to pass in the profile, the path only has one id in it, and that's the user_id.
the "dot something" at the end of a route indicates the format you want to get.
So if you type profile.json, rails will know you want json answer and will render accordingly in the controller (if this one is supported).
Other have answered about the format.
I am currently using Rails 5.1.5 and experienced similar situation. However, once I removed the instance variables that I was passing, the ids did not append to the url and you can still access them in the views.
user_profile_path(current_user, #profile)
To
user_profile_path

Rails create method failing

I'm trying to create "group posts" that are connected to a "user group" and a user. The first line of my create method is failing with this error:
Couldn't find UserGroup with 'id'=
I've been looking at blog building tutorials thinking that my "group post" is acting like a comment but instead of being attached to an article it's attached to a "user group".
I'm pretty new to Rails so it could be simply a syntax issue. Any advise would be appreciated.
Here is the group post create method:
def create
#user_group = UserGroup.find(params[:user_group_id])
#group_post = current_user.group_posts.new(group_post_params)
if #group_post.save
respond_to do |format|
format.html {redirect_to user_group_path(#user_group), notice: "Group Post created!"}
end
else
redirect_to user_group_path(#user_group), notice: "Something went wrong."
end
end
private
def group_post_params
params.require(:group_post).permit(:content, :post_type, :user_group_id)
end
Here is the user group model:
class UserGroup < ActiveRecord::Base
has_many :group_members, dependent: :destroy
has_many :members, through: :group_members, source: :user
has_many :group_posts, dependent: :destroy
validates :name, presence: true, length: {minimum: 5}
validates :searchable, presence: true, length: {minimum: 5}
def owners
members.includes(:group_members).where('group_members.owner = ?', true)
end
def regular_users
members.includes(:group_members).where('group_members.owner = ?', false)
end
end
Here is the group post model:
class GroupPost < ActiveRecord::Base
include PublicActivity::Model
belongs_to :user
belongs_to :user_group
validates :user_id, :presence => true
validates :content, :presence => true
end
And finally the routes:
Rails.application.routes.draw do
devise_for :users, controllers: {registrations: 'registrations'}
root 'pages#home'
resources :users, only: [:index, :show]
resources :friendships, only: [:create, :destroy, :accept] do
member do
put :accept
end
end
resources :posts, only: [:create, :edit, :update, :destroy]
resources :group_posts, only: [:create, :edit, :update, :destroy]
resources :activities, only: [:index] do
member do
put "upvote" =>"activities#upvote"
end
end
resources :user_groups do
resources :group_posts
resources :group_members
end
end
Here is the form partial submitting the group post:
<div class="posts-panel">
<%= form_for(#group_post) do |f| %>
<%= render 'partials/error_messages', object: f.object %>
<div class="form-group">
<%= f.text_area :content, placeholder: "Compose new post...", class: "form-control" %>
</div>
<div class="form-group">
<%= f.select :post_type, [['Request', 'request'],['Report', 'report']], {}, {class: "form-control"} %>
</div>
<div class="form-group">
<%= hidden_field_tag :user_group_id, #usergroup.id %>
<%= f.submit "Post", class: "btn btn-primary" %>
</div>
<% end %>
</div>
Complete Params:
{"utf8"=>"✓", "authenticity_token"=>"thkRQYNcl+ySSoWIE83V22DEqYdttg+TF4coFsmasXkt2mylgB2YG/vAl2KYRey/djTqL5iNSTIyWJpsSWyCQQ==", "group_post"=>{"content"=>"This is it", "post_type"=>"prayer"}, "user_group_id"=>"1", "commit"=>"Post", "controller"=>"group_posts", "action"=>"create"}
The user_group_id was not inside the params hash. Adding the following line to the create method solved the issue:
#group_post.user_group = #user_group

acts_as_votable NoMethodError (undefined method `comments' for nil:NilClass):

I'm trying to implement voting on comments within pits in my app and I keep getting a no method error. I've tried rearranging my code a number of different ways to get it to work but its not cooperating. My error in terminal shows this
Parameters: {"pit_id"=>"1", "comment_id"=>"2"}
Pit Load (0.2ms) SELECT "pits".* FROM "pits" WHERE "pits"."id" = ? LIMIT 1 [["id", 1]]
Completed 500 Internal Server Error in 2ms
NoMethodError (undefined method `comments' for nil:NilClass):
app/controllers/comments_controller.rb:22:in `upvote'
Its finding the "pit id" and the "comment id" but something is clearly a bit off. Would somebody with a better understanding of whats going on to point out what that it is. Thanks.
As it is now my code
_comment.html.erb
<div class = "well">
<p>
<strong>Comment:</strong>
<%= comment.body %>
<p>posted by: <%= comment.user.name %></p>
<%= link_to "Upvote", pit_comment_like_path(#pit, comment), method: :put, :remote => true %>
<%= link_to "Downvote", pit_comment_dislike_path(#pit, comment), method: :put, :remote => true %>
</p>
<p>
<%if comment.user == current_user %>
<%= link_to 'Destroy Comment', [#pit, comment],
method: :delete,
data: { confirm: 'Are you sure?' } %>
<% end %>
</p>
</div>
Comments Controller
class CommentsController < ApplicationController
def create
#pit= Pit.find(params[:pit_id])
#comment = #pit.comments.build(comments_params)
#comment.user = current_user
#comment.save
redirect_to pit_path(#pit)
end
def destroy
#pit = Pit.find(params[:pit_id])
#comment = #pit.comments.find(params[:id])
#comment.destroy
redirect_to pit_path(#pit)
end
def upvote
#comment = Pit.find(params[:pit_id])
#comment = #pit.comments.find(params[:id])
#comment.upvote_by current_user
redirect_to pit_path(#pit)
end
def downvote
#comment = Pit.find(params[:pit_id])
#comment = #pit.comments.find(params[:id])
#comment.downvote_by current_user
redirect_to pit_path(#pit)
end
Routes
Rails.application.routes.draw do
devise_for :users, :controllers => { registrations: 'registrations' }
devise_scope :user do
get 'users/sign_in' => 'devise/sessions#new'
get 'users/sign_out' => 'devise/sessions#destroy'
match 'users/:id', to: 'users#show', as: 'user', via: 'get'
end
resources :pits do
resources :comments do
put "like", to: "comments#upvote"
put "dislike", to: "comments#downvote"
end
end
root to: 'pages#home'
get '/about' => 'pages#about'
end
User Class
class User < ActiveRecord::Base
acts_as_voter
has_many :pits
has_many :comments
enum role: [:user, :vip, :admin]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :user
end
def name
name = first_name + ' ' + last_name
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
Comment Class
class Comment < ActiveRecord::Base
acts_as_votable
belongs_to :pit
belongs_to :user
end

Devise: allow users to reset password only

I've got a pretty standard User model with Devise. Administrators are controlled with an :admin boolean on the model, and users who aren't administrators cannot manage themselves (i.e., only administrators can make changes to users).
What I'd like is to permit users to reset their password if they forget it. They would enter their email address then be emailed a token which would grant them access to change it. I've started a solution but I really don't know how to proceed.
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
attr_accessor :current_password
attr_accessible :name, :password, :password_confirmation, :current_password, :email, :remember_me, :ftp, :colour1, :colour2, :logo, :logo2, :address, :url, :disclosure, :general_advice, :facebook, :twitter, :brand_id, :analytics
attr_protected :admin
validates_uniqueness_of :email, :ftp
belongs_to :brand
has_many :docs
has_many :orders, :through => :docs
has_attached_file :logo
has_attached_file :logo2
end
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new #guesting
if user.admin?
can :manage, :all
else
can :manage, :recoverable
end
end
end
And here's the method:
def reset
#idiot = User.where(:email => params[:email]).first
unless #idiot.nil?
Notifier.send_reset_notice(#idiot).deliver
#idiot.send_reset_password_instructions
redirect_to new_user_session_url
else
redirect_to new_user_session_url, :flash => { :error => "That email address matches no user." }
end
end
The user receives the Devise email but clicking on the link takes the user to the application root and not to a password reset form. I'm not sure how to proceed from here. Any thoughts? Cheers!
UPDATE
Relevant routes:
devise_for :users, :path => "d"
devise_scope :user do
get '/sign_in' => 'devise/sessions#new'
get '/sign_out' => 'devise/sessions#destroy'
end
First, create controller for handle Devise::PasswordsController
class PasswordusersController < Devise::PasswordsController
prepend_before_filter :require_no_authentication
append_before_filter :assert_reset_token_passed, :only => :edit
def new
super
end
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
if successfully_sent?(resource)
redirect_to root_path, :notice => "Instruction has been send to your email"
else
respond_with(resource)
end
end
def edit
super
end
def update
self.resource = resource_class.reset_password_by_token(resource_params)
if resource.errors.empty?
resource.unlock_access! if unlockable?(resource)
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
set_flash_message(:notice, flash_message) if is_navigational_format?
sign_in(resource_name, resource)
redirect_to login_path, :notice => "Password has been change"
else
respond_with resource
end
end
protected
def after_sending_reset_password_instructions_path_for(resource_name)
root_path
end
def assert_reset_token_passed
super
end
def unlockable?(resource)
super
end
end
Than, run generate view for devise
copy new.html.erb and edit.html.erb from views/devise/passwords to views/passwordusers
on routes.rb you can config such as :
devise_scope :user do
get '/reset_password' => "passowrdusers#new", :as => :reset_password
get '/new_password' => "passwordusers#edit", :as => :new_password
end
edit link on devise/mailer/reset_password_instructions.html.erb
<p><%= link_to 'Change My Password', new_password_path(#resource, :reset_password_token => #resource.reset_password_token) %></p>
finally, on view form login add this
<%= link_to "Forgot Password?", reset_password_url(resource_name) %>
UPDATE
on routes.rb you can config such as :
devise_scope :user do
get '/reset_password' => "passowrdusers#new", :as => :reset_password
get '/new_password' => "passwordusers#edit", :as => :new_password
post '/send_email' => 'passwordusers#create', :as => :create_password
put '/change' => 'passwordusers#update', :as => :update_password
end
on views/passwordusers/new.html.erb
<%= form_for("user", :url => create_password_path(resource_name), :html => { :method => :post }) do |f| %>
on views/passwordusers/edit.html.erb
<%= form_for("user", :url => update_password_path(resource_name), :html => { :method => :put }) do |f| %>

Resources