I am working on implementing unsubscribe link to my rails mailer. Unfortunately, my code breaks with this:
NoMethodError in Users#unsubscribe - undefined method `unsubscribe_hash' for nil:NilClass
which points to /app/views/users/unsubscribe.html.erb line #3
<h4>Unsubscribe from Mysite Emails</h4>
<p>By unsubscribing, you will no longer receive email...</p>
<%= simple_form_for(#user, unsubscribe_path(id: #user.unsubscribe_hash)) do |f| %>
<%= f.hidden_field(:subscription, value: false) %>
<%= f.submit 'Unsubscribe' %>
<%= link_to 'Cancel', root_url %>
<% end %>
my user_controller is as shown below
class UsersController < ApplicationController
protect_from_forgery
def new
#user = User.new
end
def create
#user = User.new(secure_params)
if #user.save
flash[:notice] = "Thanks! You have subscribed #{#user.email} for Jobs Alert."
else
flash[:notice] = 'Error Subscribing! Kindly check your email and try again.'
end
redirect_to root_path
end
def unsubscribe
user = User.find_by_unsubscribe_hash(params[:unsubscribe_hash])
#user = User.find_by_unsubscribe_hash(user)
end
def update
#user = User.find(params[:id])
if #user.update(secure_params)
flash[:notice] = 'Subscription Cancelled'
redirect_to root_url
else
flash[:alert] = 'There was a problem'
render :unsubscribe
end
end
private
def secure_params
params.require(:user).permit(:email, :subscription)
end
end
Route.rb
resources :users, only: [:new, :create]
get 'users/:unsubscribe_hash/unsubscribe' => 'users#unsubscribe', as: :unsubscribe
patch 'users/update'
user.rb
class User < ActiveRecord::Base
before_create :add_unsubscribe_hash, :add_true_to_users_table
validates :email, :uniqueness => true
validates_presence_of :email
validates_format_of :email, :with => /\A[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}\z/i
private
def add_unsubscribe_hash
self.unsubscribe_hash = SecureRandom.hex
end
def add_true_to_users_table
self.subscription = true
end
end
unsubscribe link in the email which calls unsubscribe action
# app/views/job_notifier/send_post_email.html.erb
...
<%= link_to "Unsubscribe", unsubscribe_url(id: #unsubscribe) %>.
diagrammatic view of the error
Its seems that I am missing something, do I need to define something in my users_controller? I have never in my life being able to solve NoMethodError or I don't understand what it's all about.
You get that error because #user (set in UsersController#unsubscribe) is nil. That is what the "for nil:NilClass" in undefined methodunsubscribe_hash' for nil:NilClass` is referring to.
This method doesn't seem correct:
def unsubscribe
user = User.find_by_unsubscribe_hash(params[:unsubscribe_hash])
#user = User.find_by_unsubscribe_hash(user)
end
You are looking up a user by unsubscribe_hash and assigning it to user, and then looking up user by unsubscribe_hash again but passing in user as the value to find_by_unsubscribe_hash.
I believe something like this is more as intended:
def unsubscribe
#user = User.find_by_unsubscribe_hash(params[:unsubscribe_hash])
end
Whenever you see any error messages in ruby of the format:
undefined method 'method_name' for nil:NilClass
you are being told you are trying to call something on a nil object and so the focus of your attention should be on why is that object nil. You are also told in your log where the error occurs - in your case it refers to the line, which refers to #user in #user.unsubscribe_hash in your form declaration.
So #user is nil and in this case it's nil because your controller responsible for rendering the form isn't setting #user:
user = User.find_by_unsubscribe_hash(params[:unsubscribe_hash])
#user = User.find_by_unsubscribe_hash(user)
Now quite why you are attempting to find the user and then pass that user into the second line to find #user is beyond me, but anyway the real issue is that you have no user that matches params[:unsubscribe_hash]
So the issue is related to whatever is invoking your unsubscribe action ... you have neglected to add that to your question so I cannot help with that but that is where your focus start.
Related
I'm trying to learn about services in Rails in order to avoid fat controllers.
Here a tried a simple product review creation
class ProductReviewsController < ApplicationController
before_action :set_product, only: :create
def new
#product_review = ProductReview.new
#product = Product.find(params[:product_id]) end
def create
if AddReviewService.call(product_review_params, #product)
redirect_to product_path(#product)
else
render :new
end
end
private
def set_product
#product = Product.find(params[:product_id]) end
def product_review_params
params.require(:product_review).permit(:content, :rating) end end
The thing is that in the case of wrong parameters for the review the render :new generates the following error :
screenshot of error received
Showing /home/miklw/code/michaelwautier/multitenant_app/app/views/product_reviews/new.html.erb where line #3 raised:
undefined method 'model_name' for nil:NilClass
Extracted source (around line #3):
1 <h1>New review for <%= #product.name %></h1>
2
3 <%= simple_form_for [#product, #product_review] do |f| %>
4 <%= f.input :content %>
5 <%= f.input :rating %>
6 <%= f.button :submit %>
Rails.root: /home/miklw/code/michaelwautier/multitenant_app
Application Trace | Framework Trace | Full Trace
app/views/product_reviews/new.html.erb:3:in `_app_views_product_reviews_new_html_erb__802305224391576972_70155240081100'
app/controllers/product_reviews_controller.rb:14:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"Z7YeYvXKKr7uYhANlevZ4H8U00p9givKzXzfue4pNFk0jE2DtTNY7Eacqati+V8IihSofLc2WPa4ZBzR2o0v5w==",
"product_review"=>{"content"=>"te", "rating"=>"9"},
"commit"=>"Create Product review",
"product_id"=>"4"}
Toggle session dump
Toggle env dump
Response
Headers:
None
In the error page console, if I type #product, I get the expected product object, but #product_review is nil.
BUT, if I use the regular way (see below), the form gets re-render as it should, with the notice message of the form
class ProductReviewsController < ApplicationController
before_action :set_product, only: :create
def new
#product_review = ProductReview.new
#product = Product.find(params[:product_id])
end
def create
#product_review = ProductReview.new(product_review_params)
#product_review.product = #product
if #product_review.save
redirect_to product_path(#product)
else
render :new
end
end
private
def set_product
#product = Product.find(params[:product_id])
end
def product_review_params
params.require(:product_review).permit(:content, :rating)
end
end
Any idea what could cause this issue ?
EDIT : Here is the service I call :
class AddReviewService < ApplicationService
attr_reader :content, :rating
def initialize(params, product)
#content = params[:content]
#rating = params[:rating]
#product = product
end
def call
review = ProductReview.new(content: #content, rating: #rating)
review.product = #product
return true if review.save
return false
end
end
EDIT 2 : returning the review when saved
def call
review = ProductReview.new(content: #content, rating: #rating)
review.product = #product
return review if review.save
return false
end
I have just started out programming with ruby on rails. I really like it, but sometimes it's really complicated. What I am trying to do is to get the selected value out of the select_tag and pass it to the Model where I will multiply the value to another one (that comes from an from_for textfield).
The problem is I wasn't able to figure out how to get the value from the View to the Controller and then to the Model.
Here is my code:
View:
<%= label_tag 'Remind' %>
<%= f.number_field :remind %>
<%= select_tag :select_conv, options_for_select([['Day', 1], ['Week', 7], ['Month', 30]]) %>
Controller:
def create
add = Item.new(item_params)
if add.save
flash[:notice] = ''
redirect_to items_path
else
redirect_to new_item_path
flash[:error] = ''
end
private
def item_params
params.require(:item).permit(:itemname, :amount, :bbf, :remind)
end
end
Model:
def convert_to_d
convert = self.remind * self.v_convertor
self.assign_attributes(remind: convert)
end
Thank you in advance
You have to do some changes:
View:
<%= label_tag 'Remind' %>
<%= f.number_field :remind %>
<%= select_tag :select_conv, options_for_select([['Day', 1], ['Week', 7], ['Month', 30]]) %>
From the View, it will return a hash with the values of each user's input. So, for this example, it will return:
params = { remind: user_input, select_conv: user_input }
You can catch that in your controller with the method item_params, but
you have to specify the parameters that you want in your method, so your item_params should be:
Controller:
def create
add = Item.new(item_params)
if add.save
flash[:notice] = ''
redirect_to items_path
else
redirect_to new_item_path
flash[:error] = ''
end
end
private
def item_params
params.require(:item).permit(:itemname, :amount, :bbf, :remind, :select_conv) # << update here
end
In your model, you can access the values saved in item_params with their names, as you did with self.remind, you can call it with self.select_conv.
Model:
# self.select_conv can be used now.
def convert_to_d
convert = self.remind * self.v_convertor
self.assign_attributes(remind: convert)
end
You can also use some validations in your model to guarantee integrity from the user's data. For more information about validations.
I have a simple app where users will be able to purchase items. On item pages there will be an add to cart button. The carts-controller displays the items using REDIS relationship, it has no model. Once the user has reviewed the items and wishes to buy, they will be directed to purchases view where once they pay with stripe, an order will be created in purchases-controller. The purchases controller also has no model.
The user.rb model:
class User < ActiveRecord::Base
has_many :orders
def get_cart_items
cart_ids = REDIS.smembers "cart#{id}"
Item.find(cart_ids)
end
def purchase_cart_items!(recipient_name:, recipient_address:)
get_cart_items.each { |item| purchase!(item, recipient_name, recipient_address) }
REDIS.del "cart#{id}"
end
def purchase!(item, recipient_name, recipient_address)
self.orders.create!(user_email: self.email,
item_id: item.id,
recipient_name: recipient_name)
recipient_address: recipient_address)
end
end
The purchases-controller:
class PurchasesController < ApplicationController
def create
Stripe::Charge.create(
...
)
##########
current_user.purchase_cart_items!(recipient_name:, recipient_address:)
##########
end
end
The carts-controller:
class CartsController < ApplicationController
def create
REDIS.sadd current_user_cart, params[:item_id]
end
def destroy
REDIS.srem current_user_cart, params[:item_id]
end
end
Creating the Order attributes of user_email: and item_id: is easy by calling self.email and item.id since they are attached to a model, but I am unsure of how to use a form in the carts view to have the user input the recipient_name: and recipient_address: arguments for purchase_cart_items!(recipient_name:, recipient_address:) method in the purchases-controller.
I need to:
Create a form in the carts view where the user can input arguments for a method in another controller
Upon submission of the form details, I need the user to be redirected to purchases view
The form information has to be then passed as an argument to a method in the purchases-controller
Thanks!
A form_tag with a GET method needs to be used in order to submit the form info to the right controller.
In the carts view:
<%= form_tag({controller: "purchases", action: "new"}, method: "get") do %>
<%= label_tag 'recipient name: ' %>
<%= text_field_tag 'recipient_name' %>
<%= label_tag 'recipient address: ' %>
<%= text_field_tag 'recipient_address' %>
<%= submit_tag "PAY" %>
<% end %>
Once in the new action of the purchases-controller I used params to get the form info and session to make the form information available to the create action in the same controller:
class PurchasesController < ApplicationController
def new
#name = params[:name]
#address = params[:address]
session[:name] = #name
session[:address] = #address
end
def create
Stripe::Charge.create(
...
)
##########
#name = session[:name]
#address = session[:address]
current_user.purchase_cart_destinations!(name: #name, address: #address)
##########
end
end
Once the form information was accessible in the create action of purchases-controller, the keyword arguments for current_user.purchase_cart_items! were easy to set with instance variables of #name and #address.
Please help me to resolve this error and reedit my all pages. Actually i am new to Ruby on Rails and i am using rails version-4 and ruby version-1.9.3.I want to show one form including select options and selected value saved in DB. My errors and code snippets explained below.
Error:
undefined method `email_providers=' for #<Class:0x4e68df0>
Extracted source (around line #2):
1 class Contact < ActiveRecord::Base
2 self.email_providers = %w[Gmail Yahoo MSN]
3 validates :email_provider, :inclusion => email_providers
4 end
views/contacts/index.html.erb
<%= form_for #contact,:url => {:action => "create"} do |f|%>
<%= f.text_field:gmail %>
<%= f.select :email_provider, options_for_select(Contact.email_providers, #contact.email_provider) %>
<%= f.submit "Submit"%>
<% end %>
controller/contacts_controller.rb
class ContactsController < ApplicationController
def index
#contact=Contact.new
end
def create
end
end
models/contact.rb
class Contact < ActiveRecord::Base
self.email_providers = %w[Gmail Yahoo MSN]
validates :email_provider, :inclusion => email_providers
end
migrate/20141222061313_create_contacts.rb
class CreateContacts < ActiveRecord::Migration
def change
create_table :contacts do |t|
t.string :gmail
t.string :yahoo
t.string :msn
t.timestamps
end
end
end
I want to show the 3 content(gmail,yahoo,msn) in option drop down list and while it will be selected and clicked on submit button it will be saved in DB.Please help me to edit the code.Thanks in advance..
Change
self.email_providers = %w[Gmail Yahoo MSN]
validates :email_provider, :inclusion => email_providers
in your Contact model class to:
EMAIL_PROVIDERS = %w{Gmail Yahoo MSN}
validates :email_provider, inclusion: {in: EMAIL_PROVIDERS}
and the error should be fixed.
As you can guess, your Contact class doesn't have a self.email_providers= method. So trying to assign a value to it through this method will crash. What I've done is created a constant that can be easily accessed within the class through EMAIL_PROVIDERS and outside the class through Contact::EMAIL_PROVIDERS
I'm fairly new to Ruby on Rails so apologize if this is a simple problem, but after weeks of searching for a solution, I felt it might just be easier to ask.
I'm working on a Rails 4 site where I want to have active_record based authentication. I modeled the sign up and login process after this example: http://railscasts.com/episodes/250-authentication-from-scratch?view=asciicast
This example works fine if I use cookies for my session storage, but breaks somewhere in the background when I switched it to active_record. When I try to login, it just takes me back to the main page with no flash message and nothing in my current_user (though I have done a test where I render new on the login page instead of redirect and it can find my user info, but as soon as I navigate away, I lose the session)
The cookie session won't work due to file size limitations, but we're open to other options. I've set up the initializers to point to active_records and I've added it to the gemfile, but I can't seem to figure out where it's breaking. Am I missing an insert step somewhere to add it to the db?
Another possible clue is that my protect_from_forgery line gives me a "Can't verify CSRF token authenticity" but the session still fails if I comment out that line.
I apologize if this is a very simple fix, but like I mentioned, I've been searching for a solution for a while now.
Below is the main code running it. If you'd like to see any more of the code, just let me know.
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
session_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.authenticate(params[:email], params[:password])
if user
session[:user_id] = user.id
redirect_to root_url, :notice => "Logged in! #{User.find(session[:user_id]).email}"
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
def destroy
session[:user_id] = nil
flash[:notice] = "Logged out!"
redirect_to root_url #, :notice => "Logged out!"
end
end
views/sessions/new.html.erb
<h1>Log in</h1>
<%= form_tag sessions_path do %>
<p>
<%= label_tag :email %><br />
<%= text_field_tag :email, params[:email] %>
</p>
<p>
<%= label_tag :password %><br />
<%= password_field_tag :password %>
<%= hidden_field_tag('authenticity_token', form_authenticity_token.to_s)%>
</p>
<p class="button"><%= submit_tag "Log in" %></p>
<% end %>
models/user.rb
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email, :on => :create, :message => "Can't be blank"
validates_uniqueness_of :email
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
end
Of course I would stumble upon a fix the next day after I post, but I wanted to share what I found in case others have the same problem. I started recreating the whole project from the example I shared to see if I was missing a step. When I ran that example, it started giving me a null exception, saying it was trying to create a session with no data. I took a look at the active_record github site and found this link in the "issues" section
https://github.com/rails/activerecord-session_store/issues/6
That fixed the null ref I was getting and when I plugged it into my main site it appears to have fixed the problem (or at least it doesn't log me out when I change pages). Not sure how it actually fixed it, but I'll take what I can get.
initializers/session_store.rb
ActiveRecord::SessionStore::Session.attr_accessible :data, :session_id