Getting 'undefined method `merge!' when merging SoundCloud exchange token

筅森魡賤 提交于 2020-03-06 02:24:04

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!