session method cannot be called 2 times in controller - ruby-on-rails

In the code below, when the #call method executes, it redirects the call to the #connect method to play an audio, then the #connect method redirects to #menu_selection where everything breaks. the error I get in heroku logs is that sessions is nil or defined.
What I dont understand is that I am already using session[:user_id] in the first method #call. why is it no defined in the #menu_selection method?
def call
#list = User.find_by(id: session[:user_id]).contact_lists.find_by(id: session[:last_contact_list_id])
#contacts = #list.contacts
#client = Twilio::REST::Client.new(##account_sid, ##auth_token)
#contacts.each do |contact|
#call = #client.account.calls.create(
:from => '+18056234397', # From your Twilio number
:to => '+1' + contact.phone , # To any number
:url => root_url + "connect"
)
end
redirect_to root_path
end
def connect
response = Twilio::TwiML::Response.new do |r|
r.Play 'https://clyp.it/l1qz52x5.mp3'
r.Gather numDigits: '1', action: menu_path do |g|
g.Play 'https://a.clyp.it/2mue3ocn.mp3'
end
end
render :xml => response.to_xml
end
def menu_selection
list = User.find_by(id: session[:user_id]).contact_lists.find_by(id: session[:last_contact_list_id])
user_selection = params[:Digits]
#client = Twilio::REST::Client.new(##account_sid, ##auth_token)
case user_selection
when "1"
#output = "say something."
twiml_say(#output, true)
when "2"
twiml_dial("+1805XXXXX")
when "3"
#output = "Bye Bye..."
twiml_say(#output, true)
end
end
In the #menu_selection method I get the error : undefined local variable or method `session'
Its in the first line where I'm defining the "list" variable.
I never had this kind of issue before. If anyone knows whats going on, I would appreciate your help.
I tried defining the first #list variable as a class variable outside of the method #call but It gives me the same error that I get now. I also tried making it a class variable inside the #call method to try using it in #menu_selection method, but I get an "##list is undefined" error.

Twilio developer evangelist here.
The problem here is that the session in the #call action is between your user and the server. However, when you initiate a call and Twilio calls back to your server the session between Twilio and the server is completely different.
In this situation, you need to pass the data you need Twilio to know through your URL. So, instead of just sending the connect URL, send it with the parameters you need later. So your #call action would look like:
def call
#list = User.find_by(id: session[:user_id]).contact_lists.find_by(id: session[:last_contact_list_id])
#contacts = #list.contacts
#client = Twilio::REST::Client.new(##account_sid, ##auth_token)
#contacts.each do |contact|
#call = #client.account.calls.create(
:from => '+18056234397', # From your Twilio number
:to => '+1' + contact.phone , # To any number
:url => root_url + "connect?user_id=#{session[:user_id]}&last_contact_list_id=#{session[:last_contact_list_id]}"
)
end
redirect_to root_path
end
Then, your #connect action will need to pass those on to your #menu_selection action too (note the menu_path line):
def connect
response = Twilio::TwiML::Response.new do |r|
r.Play 'https://clyp.it/l1qz52x5.mp3'
r.Gather numDigits: '1', action: menu_path(:user_id => params[:user_id], :last_contact_list_id => params[:last_contact_list_id] do |g|
g.Play 'https://a.clyp.it/2mue3ocn.mp3'
end
end
render :xml => response.to_xml
end
Finally, you will be able to use those parameters, instead of the session, in your #menu_selection action:
def menu_selection
list = User.find_by(id: params[:user_id]).contact_lists.find_by(id: params[:last_contact_list_id])
user_selection = params[:Digits]
#client = Twilio::REST::Client.new(##account_sid, ##auth_token)
case user_selection
when "1"
#output = "say something."
twiml_say(#output, true)
when "2"
twiml_dial("+1805XXXXX")
when "3"
#output = "Bye Bye..."
twiml_say(#output, true)
end
end
Let me know if this helps!

Related

NoMethodError: undefined method `sort` from Twilio voice example

I am trying to set up an example Twilio Rails project that calls a person. I am following the tutorial associated with this repo and have basically a carbon copy of the codebase. I'm getting an error that I think is from this line #validator = Twilio::Util::RequestValidator.new(##twilio_token).
Here's my twilio_controller.rb
class TwilioController < ApplicationController
# Before we allow the incoming request to connect, verify
# that it is a Twilio request
skip_before_action :verify_authenticity_token
before_action :authenticate_twilio_request, :only => [
:connect
]
##twilio_sid = ENV['TWILIO_ACCOUNT_SID']
##twilio_token = ENV['TWILIO_AUTH_TOKEN']
##twilio_number = ENV['TWILIO_NUMBER']
##api_host = ENV['TWILIO_HOST']
# Render home page
def index
render 'index'
end
def voice
response = Twilio::TwiML::Response.new do |r|
r.Say "Yay! You're on Rails!", voice: "alice"
r.Sms "Well done building your first Twilio on Rails 5 app!"
end
render :xml => response.to_xml
end
# Handle a POST from our web form and connect a call via REST API
def call
contact = Contact.new
contact.user_phone = params[:userPhone]
contact.sales_phone = params[:salesPhone]
# Validate contact
if contact.valid?
#client = Twilio::REST::Client.new ##twilio_sid, ##twilio_token
# Connect an outbound call to the number submitted
#call = #client.calls.create(
:from => ##twilio_number,
:to => contact.user_phone,
:url => "#{##api_host}/connect/#{contact.encoded_sales_phone}" # Fetch instructions from this URL when the call connects
)
# Let's respond to the ajax call with some positive reinforcement
#msg = { :message => 'Phone call incoming!', :status => 'ok' }
else
# Oops there was an error, lets return the validation errors
#msg = { :message => contact.errors.full_messages, :status => 'ok' }
end
respond_to do |format|
format.json { render :json => #msg }
end
end
# This URL contains instructions for the call that is connected with a lead
# that is using the web form.
def connect
# Our response to this request will be an XML document in the "TwiML"
# format. Our Ruby library provides a helper for generating one
# of these documents
response = Twilio::TwiML::Response.new do |r|
r.Say 'FUCK.', :voice => 'alice'
# r.Dial params[:sales_number]
end
render text: response.text
end
# Authenticate that all requests to our public-facing TwiML pages are
# coming from Twilio. Adapted from the example at
# http://twilio-ruby.readthedocs.org/en/latest/usage/validation.html
# Read more on Twilio Security at https://www.twilio.com/docs/security
private
def authenticate_twilio_request
twilio_signature = request.headers['HTTP_X_TWILIO_SIGNATURE']
# Helper from twilio-ruby to validate requests.
#validator = Twilio::Util::RequestValidator.new(##twilio_token)
# the POST variables attached to the request (eg "From", "To")
# Twilio requests only accept lowercase letters. So scrub here:
post_vars = params.reject {|k, v| k.downcase == k}
is_twilio_req = #validator.validate(request.url, post_vars, twilio_signature)
unless is_twilio_req
render :xml => (Twilio::TwiML::Response.new {|r| r.Hangup}).text, :status => :unauthorized
false
end
end
end
Error image:
I am using ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin15] and Rails 5.1.0.
Your code is most likely failing at is_twilio_req = #validator.validate(request.url, post_vars, twilio_signature) because upon inspection of the gem's code, it is failing at sort below
data = url + params.sort.join
This is because in Rails 5.1, ActionController::Parameters no longer inherits from Hash, so Hash methods like sort (see Hash docs) will no longer work.
You will need to convert params into hash explicitly:
def authenticate_twilio_request
twilio_signature = request.headers['HTTP_X_TWILIO_SIGNATURE']
#validator = Twilio::Util::RequestValidator.new(##twilio_token)
# convert `params` which is an `ActionController::Parameters` object into `Hash`
# you will need `permit!` to strong-params-permit EVERYTHING so that they will be included in the converted `Hash` (you don't need to specifically whitelist specific parameters for now as the params are used by the Twilio gem)
params_hash = params.permit!.to_hash
post_vars = params_hash.reject {|k, v| k.downcase == k}
is_twilio_req = #validator.validate(request.url, post_vars, twilio_signature)
unless is_twilio_req
render :xml => (Twilio::TwiML::Response.new {|r| r.Hangup}).text, :status => :unauthorized
false
end
end

undefined method paypal_url in Rails

I have a piece of code in Rails,
def create
#registration = Registration.new(registration_params)
if #registration.save
redirect_to #registration.paypal_url(registration_path(#registration))
else
render :new
end
end
I took it from tutorial. But I need just in this line:
#registration.paypal_url(registration_path(#registration))
Now, about my own controller, feed_controller, where
def create
#feed = Feed.new(check_params)
end
In the view erb file I put:
#feed.paypal_url(feed_path(#feed))
In my feed.rb (model):
def paypal_url(return_path)
values = {
business: "merchant#gotealeaf.com",
cmd: "_xclick",
upload: 1,
return: "#{Rails.application.secrets.app_host}#{return_path}",
invoice: id,
amount: course.price,
item_name: course.name,
item_number: course.id,
quantity: '1'
}
"#{Rails.application.secrets.paypal_host}/cgi-bin/webscr?" + values.to_query
end
Rake routes:
feed GET /:locale/feed(.:format) feed#index
feed#create POST /:locale/feed/create(.:format)
feed#new feed_new GET /:locale/feed/new(.:format)
feed#destroy feed_destroy GET /:locale/feed/destroy(.:format)
feed#edit feed_edit GET /:locale/feed/edit(.:format)
feed#update feed_update GET /:locale/feed/update(.:format)
But it prints the next error:
undefined method `paypal_url' for <#Feed::ActiveRecord_Relation:0x007fee24f5fc98>
How can I fix it? What is the problem?
UPDATE
def index
#current_user_is = current_user.email
session[:email] = #current_user_is
session[:id] = current_user.id
unless (current_user.member.present?)
#member = Member.new(:user_id => current_user.id)
#member.save()
redirect_to '/feed'
else
#new_feed = Feed.new
#feed = Feed.where(:member_id => current_user.member.id)
#category = Category.all
render 'home/uploads'
end
end
Simply use def self.paypal_url(return_path) instead of def paypal_url(return_path).
Explanation
You ran into your problem by defining a Class Method instead of an Instance Method, there's other posts discussing this.
The basic difference is, when defining:
def self.get_some_url
# code to return url of an instance
end
you can easily get the desired url of any objects, as in a view:
<% #feeds.each do |feed| %>
<%= feeds.get_some_url %>
<% end %>
Now calling Feed.get_some_url on the class would make no sense. Which url of the thousands would it call?
But there is a lot of use for class methods (where you define the method without self as you did)
def get_top_5
# code to return the top 5 most viewed feeds
end
Since this has nothing to do with a single instance, you define it for the entire Class. Leading to this call: Feed.get_top_5, which makes perfectly sense.
The second problem was not understanding the difference between where & find, this post will help you out with that.

Re-routes using a method that should have nothing to do with it

In a view I call upon a controller method. This however seems to call a different method than I want to do.
The view includes:
<%= link_to("Upgrade account", upgrade_path)
With routes:
get 'signup/organization' => 'organizations#new', as: 'register'
get 'signup/register' => 'organizations#new_premium', as: 'register_premium'
post 'signup/register' => 'organizations#checkout', as: 'signup_checkout'
post 'signup/register' => 'organizations#upgrade', as: 'upgrade'
get 'signup/confirmation' => 'organizations#confirmation'
'Upgrade' does not occur anywhere else in my routes file.
The path in the view should thus call upon the following controller method:
def upgrade
#organization = current_organization
#actioncode = Actioncode.new
#amount = DEFAULT_PRICE
#currency = "EUR"
#description = #organization.id
#transaction_description = "MyDescription"
#transaction_type = "S"
#hash = hash(#description, #amount, #currency, #transaction_type)
render 'checkout'
end
This should render a view (checkout.html.erb) which has two forms. However, instead it re-routes to the root with the message You're already logged in. As it turns out this message originates from the following controller method:
def new_premium
if (logged_in_user?)
flash[:danger] = "You're already logged in"
redirect_to root_url
end
#organization = Organization.new
#member = #organization.members.build
end
I don't see how that method can come into play and why my code isn't working. Does anyone have an idea?
The controller also contains a def checkout but I don't see how that might be of effect. The line render 'checkout' I would expect to render checkout.html.erb and have no relationship with either def checkout or def new_premium... right...?
Look at your routes:
post 'signup/register' => 'organizations#checkout', as: 'signup_checkout'
post 'signup/register' => 'organizations#upgrade', as: 'upgrade'
The same url with the same verb is used twice.
The rule in routing is "first match first served", so I guess they are all handled by checkout.
Fix is simple, change the url for one of them.

Rails 3 cookies undefined

I have a Rails 3 blog. I want every Post to have a "Like" button. Clicking on it will save this action to the database and store to cookies on the user who has just liked the Post (for disallowing same action again).
I wrote a simple action to do this:
def like
render :nothing => true
id = params[:post_id]
cookies.permanent[:like_history] ||= []
unless cookies.permanent[:like_history].include? id
cookies.permanent[:like_history] << id
#post = Post.find(id)
#post.update_column(:likes, #post.likes + 1)
end
end
But I'm getting NoMethodError (undefined method '[]' for nil:NilClass) when I try to log things. It points to this line: cookies.permanent[:like_history] ||= [] as if cookies.permanent isn't an array.
Am I doing something wrong with cookies here?
Turns out, the ||= operator counts as "reading" by rails standards, which actually makes sense. You can't "read" with cookies.permanent[:symbol], that's for writing, you read with cookies[:symbol]. So I modified that line to read:
cookies.permanent[:like_history] = "" unless defined? cookies[:like_history]
I think you have something stored in cookies.permanent[:like_history] which is not an Array. So make it nil or covert to array using to_a and try your code.
def like
render :nothing => true
cookies.permanent[:like_history] = nil #or cookies.permanent[:like_history] = cookies.permanent[:like_history].to_a
id = params[:post_id]
cookies.permanent[:like_history] ||= []
unless cookies.permanent[:like_history].include? id
cookies.permanent[:like_history] << id
#post = Post.find(id)
#post.update_column(:likes, #post.likes + 1)
end
end
Once it works remove that line you added.

How can I combine #resources and JSON results into an array of objects that share a common interface in Rails?

So, I'm building an educational resource search engine Rails app. This app will display results (e.g. Calculus videos) in a block-type layout meant to leverage Isotope (for cool filtering/sorting transitions and the like).
In addition to querying our database for appropriate Resources, we are also querying Google via their Custom Search Engine API. On top of that, eventually we will want to place some ad blocks into the grid view.
So my question is, how can I combine the #resources returned from querying our database with the JSON results of the Google query and also eventually with ads? I want to do this to make the results view (search.html.erb) as clean as possible. Additionally, I want to be able to sort/filter all of the results. That is, I'd like to be able to merge the ActiveRecord results with the Google query results. This would also allow me to do things like:
(Boxx is the generic class I'm thinking of)
<% #boxxes.each do |boxx| %>
<div class=<%= boxx.type %>>
<h2><%= boxx.title %></h2>
<h3><%= boxx.description %></h3>
...
...
...
</div>
<% end %>
My Resource controller is below. Basically, I want to combine the #resource with the results of a Google query into one enumerable with a common interface and that I can sort according to the sort type specified by the user.
What is the best way to go about this? Should I create a Boxx class at the bottom of the controller and be able to initialize it with either a Resource, google JSON or Ad? Then I could keep a type variable and then be able to sort them all together?
Here's my Resource controller
require 'will_paginate/array'
class ResourcesController < ApplicationController
def index
#resources = Resource.all
end
def create
# Usability concern here... need to make sure that they are redirected back here once they log in or something
if current_user == nil
flash[:alert] = "You must log in to submit a resource!"
redirect_to resources_path
return
else
params[:resource][:user_id] = current_user.id
end
# Check to see if this resource unique
params[:resource][:link] = Post::URI.clean(params[:resource][:link])
if unique_link?(params[:resource][:link])
#resource = Resource.new(params[:resource])
#resource[:youtubeID] = self.isYoutube(#resource[:link])
#resource.save
else
flash[:alert] = "This resource has already been added!"
end
redirect_to resources_path
end
def vote
value = params[:type] == "up" ? 1 : -1
#resource = Resource.find(params[:id])
#resource.add_or_update_evaluation(:votes, value, current_user)
respond_to do |format|
format.html { redirect_to :back, notice: "Thank you for voting" }
format.json { render :status=>200, :json=>{:success=>true}}
end
end
def isYoutube(youtube_url)
regex = %r{http://www.youtube.com}
if youtube_url[regex]
youtube_url[/^.*((v\/)|(embed\/)|(watch\?))\??v?=?([^\&\?]*).*/]
youtube_id = $5
thumbnail_Link = "http://img.youtube.com/vi/#{youtube_id}/1.jpg"
else
thumbnail_Link = nil
end
thumbnail_Link
end
def unique_link?(url)
Resource.find_by_link(url) == nil
end
def search
#resource = Resource.full_search(params[:q])
# raise params.to_s
#resource = #resource.reject!{|r| !r.media_type.eql? params[:filter][0][:media_type].downcase } if params[:filter] && !params[:filter][0][:media_type].blank?
if params[:filter]
case params[:filter][0][:sort].downcase
when 'newest'
then #resource = #resource.sort_by{|r| r.created_at}
when 'votes'
then #resource = #resource.sort_by!{|r| r.reputation_for(:votes).to_i}.reverse
else
end
end
#resource = #resource.paginate(:page => (params[:page] || 1), :per_page => 15)
end
def google(q, filter)
# Authenticating into Google's API
client = Google::APIClient.new(:key => 'secret', :authorization => nil)
# Discover the Custom Search API
search = client.discovered_api('customsearch')
# Search Google CSE
response = client.execute(
:api_method => search.cse.list,
:parameters => {
'q' => "#{q} #{filter}",
'key' => 'secret',
'cx' => 'secret'
}
)
# Decode the results
results = ActiveSupport::JSON.decode(response.body, {:symbolize_names => true})
# Return an empty array if Google CSE limit has been met.
results["items"] == nil ? [] : results["items"]
end
def make_boxxes(resources, google_results, ads)
end
end
EDIT #1: Wait, can just make a GoogleResult class, then do
#items = #resources | google_results
?
Because I could just make GoogleResult follow the same interface as Resources. But then how do I sort them? Hmmm...
I found what I needed by reading about "Designing Helpers in Ruby on Rails"
http://techspry.com/ruby_and_rails/designing-helpers-in-ruby-on-rails/

Resources