Currently I have a method called days_till_expired defined in my subscriber model. I'm trying to call it in the controller but it doesn't seem to be working? I must be missing something obvious because this should be a simple implementation. I'll post my code for clarity.
MODEL METHOD:
def days_till_expired
((created_at + 1.year) - DateTime.now).to_i / 1.day
end
CONTROLLER:
def visit
#subscriber = Subscriber.find_by_phone_number(params[:phone_number])
if #subscriber
#subscriber.visit =+ 1
#subscriber.save
flash[:notice] = "Thank You! You have #{days_till_expired} until renewal"
redirect_to subscribers_search_path(:subscriber)
else
render "search"
end
end
As you can see I'm simple trying to call the method with interpolation inside the flash notice but I keep getting this ERROR:
You are not calling to the methods, if you have days_till_expired on your model you forgot to use your model #subscriber.days_till_expired
flash[:notice] = "Thank You! You have #{#subscriber.days_till_expired} until renewal"
If I were you, I would try to move that counter logic to the model.
Related
I currently have a method that increments an attribute on the Subscriber. It's visit attribute that takes in a int. My question is - Can I find that Subscriber that last had their visit attribute updated? In the console it would look something like this - Subscriber.find("visit +=1").last <- completely wrong BTW, but I assume it would look kinda like that? Does anybody know how I can call this in the console?? Any help would be great.
Controller Method:
def visit
#subscriber = Subscriber.find_by(params[:phone_number])
if #subscriber
#subscriber.visit ||= 0
#subscriber.visit += 1
#subscriber.save
flash[:notice] = flash[:notice] = "Thank You #{#subscriber.first_name}. You have #{#subscriber.days_till_expired} until renewal"
redirect_to subscribers_search_path(:subscriber)
else
render "search"
end
end
As you can see I would like to call the Subscriber who last used this method to update the visit attribute on their object. Let me know if you need more info.
You can always get the last updated item like this:
Subscriber.order('updated_at desc').first
But :updated_at will update even if anything other than :visit is updated. So you have to write a little migration to add a custom field which will do the work for us.
rails g migration AddLastVistedToSubscriber last_visited:datetime
Run rake db:migrate to add :last_visited to our table. Now we need to update that field whenever we're doing +1 to :visit.
def visit
#subscriber = Subscriber.find_by(params[:phone_number])
if #subscriber
#subscriber.visit ||= 0
#subscriber.visit += 1
if #subscriber.save
#subscriber.touch(:last_visited) #this will update the last_visited with the update time
flash[:notice] = flash[:notice] = "Thank You #{#subscriber.first_name}. You have #{#subscriber.days_till_expired} until renewal"
redirect_to subscribers_search_path(:subscriber)
end
else
render "search"
end
end
Now we can search easily which subscriber's :visit was incremented last.
Subscriber.order('last_visited desc').first
My QuizzesController#index action looks like this:
def index
#user = current_user
#quiz = Quiz.create(user_id: current_user.id)
end
My view draws the quiz form fine. It goes to the results/index view as intended. BUT the various attributes of the quiz are NOT updated on the Quiz instance which is pulled from the database, in the QuizzesContoller#update action:
def update
#results = Quiz.where(user_id: current_user.id).last
redirect_to results_path
end
('update' is called in this case because the Quiz instance already exists, having been created in the 'index' action).
So, I tried changing the 'update' action to:
def update
#quiz.save
#results = Quiz.where(user_id: current_user.id).last
redirect_to results_path
end
But this triggers the error:
undefined method 'save' for nil:NilClass
Why is that? Shouldn't my QuizzesController have access to the #quiz variable as set up in the 'index' action? Can anyone explain what the problem is there?
Others have answered this question, so I thought I would explain why the answer is what it is. In Ruby, variables that begin with the # symbol are instance variables. This means that they are created when a new instance of their parent object is instantiated and are unique to that instance of the object.
Rails based web apps, for the most part, are stateless, meaning that state is not persisted between http requests. In layman terms, the app treats each and every request independent of all other requests. Due to this, the controllers are instanced classes. Every request instantiates a new instance of the controller class.
EDIT:
More I look at your code, you aren't following proper conventions
class QuizzesController < ApplicationController
# GET index: for displaying a list of quizzes
def index
#quizzes = Quiz.where(user_id: current_user.id)
end
# GET show: for getting a single quiz record
def show
#quiz = Quiz.find(params[:id])
end
# GET new: for initializing a new quiz record
def new
#quiz = Quiz.new
end
# POST create: for saving a new quiz record
def create
#quiz = current_user.quizzes.create(quiz_params)
if #quiz.errors
render :new
else
redirect_to #quiz #or whereever
end
end
# GET edit: for initializing existing quiz for update
def edit
#quiz = Quiz.find(params[:id)
end
# PUT/PATCH update: for updating an existing quiz record
def update
#quiz = Quiz.find(params[:id])
if #quiz.update(quiz_params)
redirect_to #quiz # or whereever
else
render :edit
end
# DELETE destroy: for deleting a quiz record
def destroy
Quiz.find(params[:id]).destroy
redirect_to :index # or whereever
end
end
You have not #quiz variable in your update action. Actions in the controller does not have access to variables in other actions.
The QuizzesController instance is not persisted between requests. The real reason instance variables are used in controllers is to pass that variable to the view.
A normal update action would look something like:
def update
#quiz = current_user.quiz # I'm assuming a user has one quiz?
#quiz.update(quiz_params) # Where quiz params takes the posted parameters from your update form
if #quiz.errors.any?
render :edit
else
redirect_to results_path
end
The key is you need to reassign #quiz with each request
Why am I getting undefined method `read' for nil:NilClass when I am trying to email an uploaded attachment?
listings_controller.rb
def send_resume_email
#listing = Listing.find(params[:id])
#user = User.find_by_id(params[:id])
UserMailer.new_resume(params[:resume].read(), params[:resume].original_filename, #user, #listing).deliver
redirect_to findjobs_path, notice: 'Message sent'
end
user_mailer.rb
def new_resume(user, listing, file, filename)
#listing = listing
#user = user
attachments[filename] = file
#url = 'http://www.new.com'
mail(to: listing.user.email, subject: 'Thanks for the awesome site')
end
I really appreciate your help.
There are several issues with your code.
First, you are trying to find two resources using the same id param. The params[:id] should belong to either the user or the listing. Which one is it?
On another note, User.find_by_id(params[:id]) is redundant. User.find(params[:id]) should suffice, since the default lookup value for find is id.
#listing = Listing.find(params[:id])
#user = User.find_by_id(params[:id])
Finally, you are calling the method read() on a params hash.
UserMailer.new_resume(params[:resume].read(), params[:resume].original_filename, #user, #listing).deliver
Where is the hash coming from? And how does params[:resume] have an instantiated object that you can call read() on? Usually param hashes are a result of form submissions or a browser request, so it is unlikely to contain an instantiated object that you can call methods on.
And this is the source of your error. The error tells you that params[:resume] is nil. Therefore calling any method on it will raise an error.
The error is just what it says:
params[:resume].nil?
would evaluate to true, and of course, nil:NilClass has no method read.
You can check that your parameter is being populated by
unless params[:resume].blank?
r = params[:resume].read
end
Ok, another annoying problem.
I have a GuestsController that with an index action like this:
def index
#booking = Booking.find(session[:booking_id]) #i have also hard coded values to make sure the session isn't the issue
#guest = Guest.find(session[:guest_id])
end
and a personal action (to perform updates) as follows:
def personal
#guest = Guest.find(session[:guest_id])
if #guest.update(post_params)
redirect_to :controller => 'guests', :action => 'cards'
else
render 'index'
end
end
My index.html.erb view uses the #booking variable:
<%= #booking.friendly_id %> #this is one example
and also contains the form to submit the "name" field to the personal action. It updates fine if the data is valid but the #booking variable doesn't exist if it's invalid???
I need to show validation errors so I can't just use redirect_to.
The error I get is: NoMethodError in Guests#personal and undefined method `friendly_id' for nil:NilClass
Any ideas?
Just initialize the object in else part
else
#booking = Booking.find(session[:booking_id])
render 'index'
end
How about moving #booking and #guest definitions to before_filter?
before_filter do
#booking = Booking.find(session[:booking_id]) #i have also hard coded values to make sure the session isn't the issue
#guest = Guest.find(session[:guest_id])
end
There needs to be something to handle when #booking is nil - which can happen if I'm reading this right.
def index
if Booking.find(session[:booking_id])?
#booking = Booking.find(session[:booking_id])
else
#booking = Booking.build #or whatever you want here
end
#guest = Guest.find(session[:guest_id])
end
I have a controller with a lot of code duplication such as:
class PostController < ApplicationController
def action1
end
...
def actionN
end
end
And basically each action do something like this:
def action
#post = Post.find(params[:id])
if #post.action(current_user)
flash[:notice] = "#{custom string for this action}"
else
flash[:notice] = "Problem with your request"
end
redirect_to root_url
end
I thought about a method in ApplicationController that takes an array of symbols and generate the other methods, such as:
def self.action_for(*args)
args.each do |method, string|
define_method method.to_sym do
#post = Post.find(params[:id])
if #post.send method.to_sym
flash[:notice] = string
else
flash[:notice] = "Problem with your request"
end
redirect_to root_url
end
end
end
And call in PostController:
action_for [:action1, "Congratulations!"], [:action2, "Cool action!"] ..
I think this solution is ugly, it makes the ApplicationController dirty and allow other controllers to call my actions.
Any idea to solve the code-duplication problem?
Why don't you make a single action which will receive some extra parameter, like msg? Then you can take advantage of built-in I18n support:
def some_action
#post = Post.find(params[:id])
if #post.action(current_user)
flash[:notice] = I18n.t("messages.#{params[:msg]}", default: "Wrong message type")
else
flash[:notice] = I18n.t("messages.problem")
end
redirect_to root_url
end
Or maybe that makes sense to allow your #post.action to return some message for your notice?
I don't think there's anything too ugly in this solution.
To limit the logic to one controller, you can define self.action_for in PostController, instead of ApplicationController, and call it below its definition.
Note that you're already passing in first elements in pairs as symbols, so to_sym calls in action_for are not necessary.