问题
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
回答1:
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
回答2:
@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...
来源:https://stackoverflow.com/questions/23646602/getting-undefined-method-merge-when-merging-soundcloud-exchange-token