Rails3 invitations and mailer - how to add variables to routes/urls? - ruby-on-rails

This should be simple fix, but I've been unable to find an answer. Can anyone point me in the right direction?
I'm implementing a Rails3 beta invite system a la Ryan Bates - http://railscasts.com/episodes/124-beta-invitations
I've set up the mailer to send out the invite urls. Everything works fine, apart from one small issue.
The url generated by the mailer is /user/sign_up.token.
I need to generate /user/sign_up/token (slash instead of period).
I guess I need to change the syntax in "Mailer.invitation().deliver", but I can't find any documentation to help. Can anyone point me in the right direction?
The relevant bits of my routes file:
devise_for :users, :path_prefix => 'registration', :controllers => {:registrations => 'users/registrations'} do
get "registration/users/sign_up/:invitation_token" => "users/registrations#new"
end
Invitations controller:
class InvitationsController < ApplicationController
def new
#invitation = Invitation.new
#title = "Invite a friend"
end
def create
#invitation = Invitation.new(params[:invitation])
#invitation.sender = current_user
if #invitation.save
if user_signed_in?
Mailer.invitation(#invitation, new_user_registration_path(#invitation.token)).deliver
redirect_to root_url, :notice => "Thank you, your friend will receive their invitation soon."
else
redirect_to root_url, :notice => "Thank you, we'll let you know when the next batch of invites are availale."
end
else
if current_user.invitation_limit > 0
render :action => 'new', :alert => "Sorry, there was a problem! Please try a again."
else
redirect_to root_url, :alert => "Sorry, you don't have any invitations left. Please wait until we issue more."
end
end
end
end
Mailer:
class Mailer < ActionMailer::Base
def invitation(invitation, sign_up)
subject 'Invitation'
recipients invitation.recipient_email
#greeting = "Hi"
#invitation = invitation
#signup_url = sign_up
#sender = invitation.sender_id
invitation.update_attribute(:send_at, Time.now)
end
end
Thank you for any ideas!

Not completely sure if this would work, but maybe try
new_user_registration_url(#invitation.token)
instead of new_user_registration_path.
Another (but not a very good) method would be
new_user_registration_url+"/#{#invitation.token}" #substitute path for url maybe
Hope this helps!

Change your route to
get "registration/users/sign_up/:id" => "users/registrations#new"
and add this to your Invitation model:
def to_param
"#{token}"
end
Then you can simply use
new_user_registration_url(#invitation)

Related

undefined method `replies_path' RoR

I am literally pulling my hair out over here.
I am very new to Ruby on Rails. I did some tutorials and projects then decided to start off on my own on my own great project.
I got pretty far and am now adding Comments to my Tutorials...
(My "logic" got very confused. In my code, a comment is actually an answer to a "post." a reply is a "comment" to a tutorial.")
I used the Rails Console to create a reply. And that worked fine. So then I added the code "copy and pasted from an earlier project" using simple_form_for to create a comment on the tutorial page.
= simple_form_for(#reply, html: { class: 'form-horizontal' }) do |c|
= c.input :content, :label => false
= c.hidden_field :tutorial_id, :value => #tutorial.id
But when I reload my tutorial show page I get this error.
NoMethodError in Tutorials#show
Showing /home/gilbert/Rails_Projects/Seek-Rails-master/app/views/tutorials/show.html.haml where line #13 raised:
undefined method `replies_path' for #<#<Class:0x007fc519554cf0>:0x007fc5182f7b70>
Did you mean? replys_path
reply_path
I'm bad enough at what I'm doing that I don't know totally what all to describe...
I think part of the problem might be a large confusion between "reply and replys" vs "reply and replies"
maybe.
Here is my tutorial_controller.rb
class TutorialsController < ApplicationController
before_action :authenticate_user!, :except => [:index, :show]
def index
#all_tutorials = Tutorial.all
# #current_user_tuts = current_user.tutorials
# # #current_user_tuts = current_user.tutorials
end
def show
#tutorial = Tutorial.find(params[:id])
#replys = #tutorial.replys
#reply = Reply.new
end
def new
#tutorial = Tutorial.new
end
def edit
#tutorial = Tutorial.find(params[:id])
end
def create
#tutorial = Tutorial.new(create_params)
if #tutorial.save
redirect_to tutorial_path(#tutorial), notice: "Tutorial Created Succesfully."
else
redirect_to :back, flash: "Aw Snap! Error saving Tutorial. Try again Later."
end
end
def create_params
params.require(:tutorial).permit(:title, :body, :user_id)
end
def destroy
#tutorial = Tutorial.find(params[:id])
if #tutorial.delete
redirect_to "/posts/profile", notice: "Tutorial Deleted"
else
redirect_to "/posts/profile", notice: "Error deleting Tutorial, please try again."
end
end
def new_reply
#reply = Reply.new
end
end
And my replys_controller.rb
class ReplysController < ApplicationController
before_action :authenticate_user!, :except => [:index, :show]
def new
#reply = Reply.new
end
def create
#reply = Reply.new(create_params)
if #reply.save
redirect_to :back, notice: "Comment Created Succesfully."
else
redirect_to :back, flash: "Aw Snap! Error saving Comment. Try again Later."
end
end
def create_params
params.require(:reply).permit(:content, :tutorial_id, :user_id)
end
end
and Routes.rb
Rails.application.routes.draw do
devise_for :users
root to: "posts#index"
get "/posts/forum" => "posts#forum"
get "/posts/profile" => "posts#profile"
get "/posts/edit" => "posts#edit"
get "/posts/search" => "posts#search"
get "/tutorials/index" => "tutorials#index"
resources :posts
resources :comments
resources :tutorials
resources :replys
end
and reply.rb (model)
class Reply < ActiveRecord::Base
belongs_to :tutorial
end
and tutorial.rb (model)
class Tutorial < ActiveRecord::Base
belongs_to :user
has_many :replys
end
Sorry for the long post. I was hoping that it would suddenly come to me as I wrote it but it didn't.
I'd really appreciate the time spent on debugging this.
My code is also on github if you want to look at it or fork it or whatever...
https://github.com/GilGiy/Walk-Rails/tree/master/app
And if you have any tips about my code or something I'm doing completely wrong plz tell me...
Thanks a lot for you time....
Gil
EDIT:
Here's the output of "rake routes | grep replys"
replys GET /replys(.:format) replys#index
POST /replys(.:format) replys#create
new_reply GET /replys/new(.:format) replys#new
edit_reply GET /replys/:id/edit(.:format) replys#edit
reply GET /replys/:id(.:format) replys#show
PATCH /replys/:id(.:format) replys#update
PUT /replys/:id(.:format) replys#update
DELETE /replys/:id(.:format) replys#destroy
The problem is with you route resources :replys.
Your model name is Reply and its plural is replies so when you create a form with:
simple_form_for #reply
Rails will detect that #reply is a Reply object and try to create a path automatically by pluralizing the name i.e replies_path but you don't have that route, instead you have replys_path so its giving the error.
Though you can explicitly mention the url and change the path to correct one, I would highly suggest you to change the route to resources :replies to avoid this kind of problem in future.

Template is missing in action

I wrote a "follow" method in UsersController
def start_following
#user = current_user
#user_to_follow = User.find(params[:id])
unless #user_to_follow == #user
#follow_link = #user.follow_link.create(:follow_you_id => #user_to_follow.id, :user_id => #user.id)
#user.save
flash[:start_following] = "You started following" + #user_to_follow.name
else
flash[:cant_follow] = "You cannot follow yourself"
end
end
Pretty simple. And In the view, I have
<%= link_to 'Follow', follow_user_path(#user) %>
In routes,
resources :users do
member do
get 'follow' => "users#start_following", :as => 'follow'
When I click on the link, it complains: Missing template users/start_following
So, how do I make it just stay on the same page after the action?
The view page that I want to stay on is Show view of the user is to be followed.
ex: users/{user_id}. Is simply redirecting not a solution? I thought adding redirect_to {somewhere} would get rid of the error, but it didn't.
I would redirect to the user in question. If you are using the standard resourceful routes then you can just do
redirect_to(#user_to_follow)
As an aside it's generally considered bad practice to have GET requests that make changes - people normally use put/patch/post/delete requests for those. You can fall afoul of browsers pre-fetching links without the user actually clicking on them.
try:
redirect_to :back, :notice => "successfully followed someone..."
Yes redirect_to solves your problem, I suspect you forgot to add it to both branches of the unless
The code would look like this:
def start_following
#user = current_user
#user_to_follow = User.find(params[:id])
unless #user_to_follow == #user
#follow_link = #user.follow_link.create(:follow_you_id => #user_to_follow.id, :user_id => #user.id)
#user.save
flash[:start_following] = "You started following" + #user_to_follow.name
else
flash[:cant_follow] = "You cannot follow yourself"
end
redirect_to #user_to_follow
end

Rails - Mailer Instance Variable Nil in Email View

I am attempting to create beta invitations using the structure from railscasts episode 124, updated for rails 3.2.8.
Currently, the invitation email gets sent, but does not contain the url (which includes the invitation token) for users to follow to sign up because the instance variable I am creating in ActionMailer (#invitation_link) is nil in the view. Inspecting #invitation_link in the ActionMailer controller shows that it is pointing to the correct url, but it is nil in the view.
I have also checked out the following questions and none of the solutions have worked for me:
How do you use an instance variable with mailer in Ruby on Rails?
https://stackoverflow.com/questions/5831038/unable-to-access-instance-variable-in-mailer-view
Actionmailer instance variable problem Ruby on Rails
ActionMailer pass local variables to the erb template
Relevant code snippets below:
invitations_controller.rb
class InvitationsController < ApplicationController
def new
#invitation = Invitation.new
end
def create
#invitation = Invitation.new(params[:invitation])
#invitation.sender = current_user
if #invitation.save
if signed_in?
InvitationMailer.invitation(#invitation).deliver
flash[:notice] = "Thank you, invitation sent."
redirect_to current_user
else
flash[:notice] = "Thank you, we will notify when we are ready."
redirect_to root_url
end
else
render :action => 'new'
end
end
end
in invitation_mailer.rb file
class InvitationMailer < ActionMailer::Base
default from: "holler#thesite.com", content_type: "text/html"
def invitation(invitation)
mail to: invitation.recipient_email, subject: "Invitation"
#invitation_link = invited_url(invitation.token)
invitation.update_attribute(:sent_at, Time.now)
end
end
views/invitation_mailer/invitation.text.erb
You are invited to join the site!
<%= #invitation_link %> # INSTANCE VARIABLE THAT IS NIL IN VIEW
routes.rb (only showing relevant line)
match '/invited/:invitation_token', to: 'users#new_invitee', as: 'invited'
try this way
This is your InvitationMailer
def invitation(invitation)
#invitation = invitation
mail(:to => #invitation.recipient_email, :subject => "Invitation")
end
now, in your InvitationsController
if signed_in?
#invitation.update_attribute(:sent_at, Time.now)
InvitationMailer.invitation(#invitation).deliver
...
else
...
end
now, views/invitation_mailer/invitation.text.erb
You are invited to join the site!
<%= invited_url(#invitation.token) %> # INSTANCE VARIABLE THAT IS NIL IN VIEW
try this...
#invitation_link = invited_url(invitation.token, :host => "localhost:3000")

Advanced contraints in rails 3 for user-centric routing

Learning rails development and would usually prefer to search out an answer than waste peoples time but this has been doing my head in all night.
Essentially I'm trying to present user-dependant views ala github etc.
I'm trying to follow the instructions laid out here:
http://collectiveidea.com/blog/archives/2011/05/31/user-centric-routing-in-rails-3/
My authentication at the moment is from the railscast "Authentication from Scratch - revised" which uses sessions, my sessions_crontroller.rb:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Logged in!"
else
flash.now.alert = "Email or password is invalid"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Logged out!"
end
end
And my routes.rb:
C::Application.routes.draw do
root :to => "static_pages#home", :constraints => LoggedInConstraint.new(false)
root :to => "users#show", :constraints => LoggedInConstraint.new(true)
resources :users
resources :sessions
As per my understanding, because I'm not using cookies the final comment under that blog posts recommends using request.session[:your_key] in place of request.cookies.key?("user_token") however when logged in I am still taken to static_pages#home? If anyone could shed some light on the topic I would very much appreciate it.
I also apologise for any formatting errors etc, this is my first question on stackoverflow.
Thanks again!
Not sure about your exact question, but I just did something kind of similar to this, so maybe my code will help you:
My routes:
# Except from config/routes.rb
require File.expand_path("../../lib/role_constraint", __FILE__)
MyApp::Application.routes.draw do
mount Resque::Server, :at => "/resque", :constraints => RoleConstraint.new('admin')
...
...
...
My constraint:
# lib/role_constraints.rb
class RoleConstraint < Struct.new(:value)
def matches?(request)
request.session[:role] == value
end
end
My sessions controller:
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
before_filter :require_user, :only => :destroy
def new
end
def create
user = User.find_by_username(params[:username])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
# Just for /resque
# Not secure - if you change a user's role, it will not be updated here
# until they log out and log in again.
session[:role] = user.role
if user.email.nil?
redirect_to user, :notice => "Please add your email address to your account"
else
redirect_to root_url, :notice => "Logged in!"
end
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
def destroy
session[:user_id] = nil
session[:current_project_id] = nil
redirect_to root_url, :notice => "Logged out!"
end
end

Rails User Sign Up Mail Confirmation

I'm trying to create a mailer that sends out an email whenever a user signs up. Pretty simple but I'm new to rails.
I have a site that already creates the user. I have a login and sign up page that works correctly, but need some help creating a mailer that sends out an email confirmation link and possibly an option to send out these emails without the user signing up like make a separate page for user invitations.
I've generated a model invitation.rb
class Invitation < ActiveRecord::Base
belongs_to :sender, :class_name => 'User'
has_one :recipient, :class_name => 'User'
validates_presence_of :recipient_email
validate :recipient_is_not_registered
validate :sender_has_invitations, :if => :sender
before_create :generate_token
before_create :decrement_sender_count, :if => :sender
private
def recipient_is_not_registered
errors.add :recipient_email, 'is already registered' if User.find_by_email(recipient_email)
end
def sender_has_invitations
unless sender.invitation_limit > 0
errors.add_to_base 'You have reached your limit of invitations to send.'
end
end
def generate_token
self.token = Digest::SHA1.hexdigest([Time.now, rand].join)
end
def decrement_sender_count
sender.decrement! :invitation_limit
end
#attr_accessible :sender_id, :recipient_email, :token, :sent_at
end
and my invitiation_controller.rb
class InvitationsController < ApplicationController
def new
#invitation = Invitation.new
end
def create
#invitation = Invitation.new(params[:invitation])
#invitation.sender = current_user
if #invitation.save
if logged_in?
Mailer.deliver_invitation(#invitation, signup_url(#invitation.token))
flash[:notice] = "Thank you, invitation sent."
redirect_to projects_url
else
flash[:notice] = "Thank you, we will notify when we are ready."
redirect_to root_url
end
else
render :action => 'new'
end
end
end
What else do I need to edit? how do I hook this up to an already existing user signup and login that is working fine?
You should already have a UsersController or something like that for registration purposes, which you currently access through the signup_url named route. Suppose that this route is now something like:
http://localhost:3000/register/code_here
All you have to do now is check for the invitation in the controller action and process it accordingly like so:
def new
invite = Invite.find_by_token(params[:id]
if invite.nil?
redirect_to root_path, :notice => "Sorry, you need an invite to register"
end
#user = User.new(:email => invite.recipient_email)
end
def create
invite = Invite.find_by_token(params[:token]
if invite.nil?
redirect_to root_path, :notice => "Sorry, you need an invite to register"
end
begin
invite.nil.transaction do
invite.nil.destroy!
#user = User.create(params[:user)
end
redirect_to my_dashboard_path, :notice => "Yay!"
rescue ActiveRecord::RecordInvalid => invalid
render :new, :alert => "Validation errors"
end
end
Without the invite code, you will simply redirect to root page. You may want to DRY that check though. When someone uses the invite code, you may want to delete it from the database. I wrapped it up in a transaction, but this is up to you (creating the user may be more important).
If you want to create a page that allows users to create invitations without signing up, then simply don't add authentication to InvitationsController and update this snippet:
def create
#invitation = Invitation.new(params[:invitation])
#invitation.sender = current_user if logged_in?
if #invitation.save
Mailer.deliver_invitation(#invitation, signup_url(#invitation.token))
flash[:notice] = "Thank you, invitation sent."
if logged_in?
redirect_to projects_url
else
redirect_to root_url
end
else
render :action => 'new'
end
end
I'm not sure if I covered all the bases, but I think this should point you in the right direction at least.
I can not see where Mailer.deliver_invitation comes from, do you use a gem? would it help if you would create mailer.rb, do you have any error mgs/ stack trace?
Have a look here there are some guides, 5 Action Mailer Configuration
http://guides.rubyonrails.org/action_mailer_basics.html
Consider using devise for user authentication, https://github.com/plataformatec/devise
It is complex, but well documented and easy to configure to jump start.
I assume you are using Rails 3.1 (works also in earlier versions, just find the right guide to your Rails version, to be sure)

Resources