I'm working on an app that interacts with SoundCloud and I'm having an issue when I try to save the exchange_token that I'm getting back from the server (among other things) and I really could use some assistance.
According to the error I'm getting:
undefined method `merge!' for nil:NilClass
The problem apparently lies with line 10 in my sclouds_controller.rb file (included below):
soundcloud_client.exchange_token(:code => params[:code])
Which is calling a method in the SoundCloud gem that I'm using. Here's the line in the SoundCloud gem that the error originates from:
params.merge!(client_params)
That can be found on line 23 of the following method (taken from the client.rb file in the SoundCloud gem):
def exchange_token(options={})
store_options(options)
raise ArgumentError, 'client_id and client_secret is required to retrieve an access_token' if client_id.nil? || client_secret.nil?
client_params = {:client_id => client_id, :client_secret => client_secret}
params = if options_for_refresh_flow_present?
{
:grant_type => 'refresh_token',
:refresh_token => refresh_token,
}
elsif options_for_credentials_flow_present?
{
:grant_type => 'password',
:username => #options[:username],
:password => #options[:password],
}
elsif options_for_code_flow_present?
{
:grant_type => 'authorization_code',
:redirect_uri => #options[:redirect_uri],
:code => #options[:code],
}
end
params.merge!(client_params)
response = handle_response(false) {
self.class.post("https://#{api_host}#{TOKEN_PATH}", :query => params)
}
#options.merge!(:access_token => response.access_token, :refresh_token => response.refresh_token)
#options[:expires_at] = Time.now + response.expires_in if response.expires_in
#options[:on_exchange_token].call(*[(self if #options[:on_exchange_token].arity == 1)].compact)
response
end
However, if I throw a 'raise' in my sclouds_controller.rb file like this:
def connected
if params[:error].nil?
raise
soundcloud_client.exchange_token(:code => params[:code])
Then, in the console, manually paste in the following line:
soundcloud_client.exchange_token(:code => params[:code])
I get back the following response (which, to me, appears to be successful):
$ #<SoundCloud::HashResponseWrapper access_token="xxxxx" expires_in=21599 refresh_token="xxxxx" scope="*">
Any idea why this is happening? I'm trying to learn what I'm doing wrong, especially since I'm not sure if I'm going about this in the right way. Here's some of my code for a little more context. Thanks in advance!
sclouds_controller.rb:
class ScloudsController < ApplicationController
before_action :authenticate_user!, only: [:connect, :connected]
def connect
redirect_to soundcloud_client.authorize_url(:display => "popup")
end
def connected
if params[:error].nil?
soundcloud_client.exchange_token(:code => params[:code])
unless user_signed_in?
flash[:alert] = 's'
redirect_to :login
end
current_user.update_attributes!({
:soundcloud_access_token => soundcloud_client.access_token,
:soundcloud_refresh_token => soundcloud_client.refresh_token,
:soundcloud_expires_at => soundcloud_client.expires_at
})
end
redirect_to soundcloud_client.redirect_uri
end
def disconnect
login_as nil
redirect_to root_path
end
private
def soundcloud_client
return #soundcloud_client if #soundcloud_client
#soundcloud_client = User.soundcloud_client(:redirect_uri => 'http://localhost:3000/sclouds/connected/')
end
end
user.rb:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessor :soundcloud_access_token, :soundcloud_refresh_token, :soundcloud_expires_at
has_one :scloud
#SOUNDCLOUD_CLIENT_ID = 'xxxxx'
#SOUNDCLOUD_CLIENT_SECRET = 'xxxxx'
#REDIRECT_URI = 'xxxxx'
def self.soundcloud_client(options={})
options = {
:client_id => #SOUNDCLOUD_CLIENT_ID,
:client_secret => #SOUNDCLOUD_CLIENT_SECRET,
:redirect_uri => #REDIRECT_URI
}.merge(options)
Soundcloud.new(options)
end
def soundcloud_client(options={})
client = self.class.soundcloud_client(options)
options= {
:access_token => soundcloud_access_token,
:refresh_token => soundcloud_refresh_token,
:expires_at => soundcloud_expires_at
}.merge(options)
client.on_exchange_token do
self.update_attributes!({
:soundcloud_access_token => client.access_token,
:soundcloud_refresh_token => client.refresh_token,
:soundcloud_expires_at => client.expires_at
})
end
client
end
end
options= {
:access_token => soundcloud_access_token,
:refresh_token => soundcloud_refresh_token,
:expires_at => soundcloud_expires_at
}.merge(options)
How does this work?
You're trying to set the options local var (which is a duplicate of the options argument in your method), and then you're trying to merge options? Looks like a self-referential loop to me...
Method
Secondly, you mention the error is caused by this line:
exchange_token(:code => params[:code])
I can't see this method in your documentation? The merge! method is obviously being caused inside this method somewhere - we need to know where it's being called, so we can fix it!
I can see one mention of merge, which I've queried above
Update
Thanks for posting the code!
I think #mischa is right - but I'll keep this code to show you what I'd look at..
I can only give an opinion, as I've never used this gem before -There are two calls to merge! in the gem code:
params.merge!(client_params)
#options.merge!(:access_token => response.access_token, :refresh_token => response.refresh_token)
I'd look at this:
Params
The params hash is populated locally like this:
params = if options_for_refresh_flow_present?
{
:grant_type => 'refresh_token',
:refresh_token => refresh_token,
}
elsif options_for_credentials_flow_present?
{
:grant_type => 'password',
:username => #options[:username],
:password => #options[:password],
}
elsif options_for_code_flow_present?
{
:grant_type => 'authorization_code',
:redirect_uri => #options[:redirect_uri],
:code => #options[:code],
}
end
This could be the cause of the problem, as it is populated with elsif (not else). This means you have to make sure you're passing the right codes to the system
#options
#options is referenced but not declared
I imagine it's declared in another part of the gem code, meaning you need to make sure it's there. This will likely be set when the gem initializes (I.E when you set up the authentication between SC & your app)
If you've not got the #options set up correctly, it will probably mean you're not calling the gem correctly, hence the error.
Try mischa's fix, and see if that cures the issue for you.
Initializer
Something to note - convention for including gems in your system is to use an initializer
If you set up an initializer which creates a new constant called SOUNDCLOUD, you can then reference that throughout your app (curing any errors you have here)
I can write some code for you on this if you want
#RickPeck got very close I think, as the problem really lies in this code excerpt:
params = if options_for_refresh_flow_present?
{
:grant_type => 'refresh_token',
:refresh_token => refresh_token,
}
elsif options_for_credentials_flow_present?
{
:grant_type => 'password',
:username => #options[:username],
:password => #options[:password],
}
elsif options_for_code_flow_present?
{
:grant_type => 'authorization_code',
:redirect_uri => #options[:redirect_uri],
:code => #options[:code],
}
end
#options is populated in the line store_options(options). So the problem is that neither options_for_refresh_flow_present?, options_for_credentials_flow_present?, nor options_for_code_flow_present? return true.
The relevant option for your code is code flow:
def options_for_code_flow_present?
!!(#options[:code] && #options[:redirect_uri])
end
which expects options to have both :code and :redirect_uri. In your code you pass only :code. Add a :redirect_uri and you should be good to go.
What #Mischa suggests will probably fix that for you, as your :redirect_uri was nil when you set it...
Related
Is there any reference available to connect to Facebook using Oauth2 gem in Rails 4?
I am getting above error while calling get_token method of Oauth2.
def login_facebook
redirect_to client.auth_code.authorize_url(
:redirect_uri => 'http://localhost:3000/oauth/facebook_callback'
)
end
def facebook_callback
token = client.auth_code.get_token(
params[:code],
:token_url => "/oauth/token",
#:token_url => "/oauth/access_token",
# :parse => :query,
:redirect_uri => 'http://localhost:3000/oauth/facebook_callback'
)
puts "**************"
puts token.inspect
puts "**************"
end
private
def client
OAuth2::Client.new(APPLICATION_ID, APPLICATION_SECRET, :site => FB_GRAPH_URL)
end
The default url for requesting the access token has been changed from /oauth/access (which is what FB uses) to /oauth/access_token. Now, you need to override this setting as:
OAuth2::Client.new(FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, {
:token_url => '/oauth/access_token',
:redirect_uri => 'http://localhost:3000/oauth/facebook_callback'
})
You may want to use Koala gem for easy communication with facebook.
I am using Devise + Omniauth to enable Facebook signup in my application. When I was developing it, I encountered no problems. Same with deploying it to my remote server. The problem is, other people keep encountering the same error:
TypeError (no implicit conversion of Symbol into Integer):
app/models/user.rb:67:in `find_for_facebook_oauth'
app/controllers/users/omniauth_callbacks_controller.rb:4:in `facebook'
I have the following code for the User model user.rb:
def self.find_for_facebook_oauth( data, signed_in_resource=nil)
user = User.where(:email => data.info.email).first
unless user
params =
{
:user =>
{
:username => data.uid,
:email => data.info.email,
:password => Devise.friendly_token[0,20],
:user_profile_attributes =>
{
:first_name => data.extra.raw_info.first_name,
:last_name => data.extra.raw_info.last_name,
:remote_image_url => data.extra.raw_info.image,
},
:user_auths_attributes =>
{
:uid => data.uid,
:provider => data.provider
}
}
}
user = User.create!(params[:user])
end
return user
end
Where line 67 is the user = User.create!(params[:user])
And omniauth_callbacks_controller.rb:
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.find_for_facebook_oauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
Where line 4 is #user = User.find_for_facebook_oauth(request.env["omniauth.auth"])
The server logs also show the GET parameters:
Parameters: {"code"=>"[some long string of number and letters]", "state"=>"[another string of numbers and letters]"}
Update:
The logger outputs the following for request.env["omniauth.auth"]:
#<OmniAuth::AuthHash credentials=#<OmniAuth::AuthHash expires=true expires_at=1401992074 token="*"> extra=#<OmniAuth::AuthHash raw_info=#<OmniAuth::AuthHash email="*" first_name="*" gender="male" id="*" last_name="*" link="https://www.facebook.com/*" locale="en_US" name="*" timezone=8 updated_time="2014-04-05T09:29:22+0000" username="*" verified=true>> info=#<OmniAuth::AuthHash::InfoHash email="*" first_name="*" image="http://graph.facebook.com/*/picture?type=square" last_name="*" name="*" nickname="*" urls=#<OmniAuth::AuthHash Facebook="https://www.facebook.com/*"> verified=true> provider="facebook" uid="*">
Update 2:
Logging the params[:user] provides the following values:
Params: {:username=>"*", :email=>"*", :password=>"iePVLt7XEWk4YwPjja6n", :user_profile_attributes=>{:first_name=>"*", :last_name=>"*", :remote_image_url=>"http://graph.facebook.com/*/picture?type=square"}, :user_auths_attributes=>{:uid=>"*", :provider=>"facebook"}}
Update your params hash as below:
params =
{
:user =>
{
:username => data.uid,
:email => data.info.email,
:password => Devise.friendly_token[0,20],
:user_profile_attributes =>
{
:first_name => data.extra.raw_info.first_name,
:last_name => data.extra.raw_info.last_name,
:remote_image_url => data.info.image ## Removed comma and updated the method
},
:user_auths_attributes =>
[{
:uid => data.uid,
:provider => data.provider
}] ## Enclosed within array [] brackets
}
}
Looking at the params hash given by you, I can tell that a User and Profile have a 1-1 Relationship whereas User and Auths has a 1-M Relationship. In that case, user_auths_attributes must be passed as an Array.
TypeError (no implicit conversion of Symbol into Integer)
You were getting the above error because user_auths_attributes was being interpreted as an array and not a hash. So when Ruby saw params[:user][:user_auths_attributes][:uid] it was trying to take the last key and turn it into params[:user][:user_auths_attributes][0] or at least find some integer value it could be converted to index the Array.
I found only this issue:
:remote_image_url => data.extra.raw_info.image # In data I see only data.info.image
replace with
:remote_image_url => data.info.image
But it is not a solution for your question.
Try to debug data from params[:user]. From exception it looks like that you use some Hash on property which is Integer.
I have designed a ruby on rails 3.2 application in which I have a Users model. I wrote a custom function in order to authenticate userName and password through another mobile application.
Everything is working fine in localhost and I am able to authenticate from the mobile application. So, I went a step further and deployed it in Heroku. Now the authenticate function is not working.
In my Gem File I have included the following:
group :production do
gem 'pg'
end
group :development do
gem 'sqlite3'
end
In my users_controller.rb I have written my custom authenticate function:
# http://wgo-ror.herokuapp.com/users/authenticate?userName=ramy&password=123456
def authenticate
usr = params[:userName]
pwd = params[:password]
#getUser = User.where("userName = ? AND password = ?", params[:userName], params[:password])
if (#getUser!=[])
respond_to do |format|
format.html { render :json => {:Success => true, :Data => #getUser}, :callback => params[:callback] }
format.json { render :json => {:Success => true, :Data => #getUser}, :callback => params[:callback] }
end
else
respond_to do |format|
format.html { render :json => {:Success => false, :Data => #getUser}, :callback => params[:callback] }
format.json { render :json => {:Success => false, :Data => #getUser}, :callback => params[:callback] }
end
end
end
I have also written another custom function called saveUser. This particular function is working fine even after I deployed it to heroku.
# http://wgo-ror.herokuapp.com/users/saveUser?id=1&userName=yrkapil&password=123456&email=yrkapil#gmail.com
def saveUser
#user = User.find(params[:id])
#user.update_attributes(:userName => params[:userName], :password => params[:password], :email => params[:email] )
end
In my routes.rb I have included the following:
match 'users/authenticate' => 'users#authenticate', :as => :authenticate
match 'users/saveUser' => 'users#saveUser', :as => :saveUser
resources :users
resources :festivals
root :to => 'users#index'
Am I missing something or is it because of postgresql? I am not getting any clue of how to do it. Please help me Guys. I really need to get this working. Thanks in advance!
You probably have a case issue in your userName column.
You created your userName column with something like this:
t.string :userName
and that would cause Rails to send SQL like this to the database:
create table users (
"userName" varchar(255),
...
)
Note the quotes around the userName identifier; the quotes make that column name case sensitive. But, unquoted identifiers in PostgreSQL are folded to lower case (the SQL standard says upper case but that's immaterial here). Essentially the quotes mean that you always have to say "userName" when referring to the column, you can't say username or userName or anything else.
SQLite, on the other hand, has a very loose interpretation of SQL so you can do things like this:
create table t ("userName" text);
insert into t values ('x');
select * from t where username = 'x';
and everything will work.
Every time you let ActiveRecord build the column name, it will quote it so this:
#user.update_attributes(:userName => params[:userName], :password => params[:password], :email => params[:email] )
will end up doing this:
update users
set "userName" = ...
and everyone is happy. However, you are using an SQL fragment right here:
#getUser = User.where("userName = ? AND password = ?", params[:userName], params[:password])
without quoting your userName identifier so PostgreSQL is probably complaining about an unknown column. If you did this:
#getUser = User.where('"userName" = ? AND password = ?', params[:userName], params[:password])
you'd have better luck. Or you could do this:
#getUser = User.where(:userName => params[:userName], :password => params[:password])
or this:
#getUser = User.where(:userName => params[:userName]).where(:password => params[:password])
You should do two things:
Stop using mixed case column names. Lower case column and table names with underscores to separate words is the usual practice with PostgreSQL (and Ruby and Rails for that matter).
Stop developing on top of SQLite and start developing on top of PostgreSQL if you're deploying to Heroku. Your development stack and your deployment stack should be the same (or as close to identical as possible).
I've a got a method in ActiveRecord::User:
def create_user_from_json(user)
#user=User.new(user)
if #user.save!
#user.activate!
end
end
And I'm trying to call it in a plugin's module method. The plugin is json-rpc-1-1. Here is the relevant code:
class ServiceController < ApplicationController
json_rpc_service :name => 'cme', # required
:id => 'urn:uuid:28e54ac0-f1be-11df-889f-0002a5d5c51b', # required
:logger => RAILS_DEFAULT_LOGGER # optional
json_rpc_procedure :name => 'userRegister',
:proc => lambda { |u| ActiveRecord::User.create_user_from_json(u) },
:summary => 'Adds a user to the database',
:params => [{:name => 'newUser', :type => 'object'}],
:return => {:type => 'num'}
end
The problem is the code in the proc. No matter whether I call ActiveRecord::User.create_user_from_json(u) or ::User.create_user_from_json(u) or User.create_user_from_json(u) I just get undefined method 'create_user_from_json'.
What is the proper way to call a User method from the proc?
I think this needs to be a class method instead of an instance method, declare it like this:
def self.create_user_from_json(user)
...
end
Trying to test a controller in Rspec. (Rails 2.3.8, Ruby 1.8.7, Rspec 1.3.1, Rspec-Rails 1.3.3)
I'm trying to post a create but I get this error message:
ActiveRecord::AssociationTypeMismatch in 'ProjectsController with appropriate parameters while logged in: should create project'
User(#2171994580) expected, got TrueClass(#2148251900)
My test code is as follows:
def mock_user(stubs = {})
#user = mock_model(User, stubs)
end
def mock_project(stubs = {})
#project = mock_model(Project, stubs)
end
def mock_lifecycletype(stubs = {})
#lifecycletype = mock_model(Lifecycletype, stubs)
end
it "should create project" do
post :create, :project => { :name => "Mock Project",
:description => "Mock Description",
:owner => #user,
:lifecycletype => mock_lifecycletype({ :name => "Mock Lifecycle" }) }
assigns[:project].should == mock_project({ :name => "Mock Project",
:description => "Mock Description",
:owner => mock_user,
:lifecycletype => mock_lifecycletype({ :name => "Mock Lifecycle" })})
flash[:notice].should == "Project was successfully created."
end
The trouble comes when I try to do :owner => #user in the code above. For some reason, it thinks that my #user is TrueClass instead of a User class object. Funny thing is, if I comment out the post :create code, and I do a simple #user.class.should == User, it works, meaning that #user is indeed a User class object.
I've also tried
:owner => mock_user
:owner => mock_user({ :name => "User",
:email => "user#email.com",
:password => "password",
:password_confirmation => "password })
:owner => #current_user
Note #current_user is also mocked out as a user, which I tested (the same way, #current_user.class.should == User) and also returns a TrueClass when I try to set :owner.
Anybody have any clue why this is happening?
Thank you!
From what I can see, you are not creating your instance variable, #user before referencing it in the post statement. You would do well to create the instance variables prior to the post so the preconditions are immediately obvious. That way you could know whether #user had been set.
I know some people prefer the one-line-of-code-is-better-because-i'm-smart method of writing stuff like this, but I've found being explicit and even repetitive is a really good idea, particularly in tests.
I'm adding the following code that I believe may express your intent better that what you have. In my code, I use mock expectations to "expect" a Project is created with a particular set of parameters. I believe your code assumes that you can do an equality comparison between a newly-created mock Project and a different one created during execution of your controller. That may not be true because they are distinctly different objects.
In my code, if you have a problem with something evaluating to TrueClass or the like, you can use a line of code like user.should be_a(User) to the example to make sure stuff is wired up correctly.
def mock_user(stubs = {})
mock_model(User, stubs)
end
def mock_project(stubs = {})
mock_model(Project, stubs)
end
def mock_lifecycletype(stubs = {})
mock_model(Lifecycletype, stubs)
end
it "should create project" do
user = mock_user
owner = user
lifecycletype = mock_lifecycletype({ :name => "Mock Lifecycle" })
# Not certain what your params to create are, but the argument to with
# is what the params are expected to be
Project.should_receive(:create).once.with({:user => user, :owner => owner, :lifecycletype => lifecycletype})
post :create, :project => { :name => "Mock Project",
:description => "Mock Description",
:owner => #user,
:lifecycletype => lifecycletype }
flash[:notice].should == "Project was successfully created."
end