2017-02-22 8 views
1

У меня нет большого опыта работы с модулями mixin. Тогда, пожалуйста, простите меня, если мой вопрос кажется немного наивным.У вас возникли проблемы со спецификациями для модуля Ruby mixin

Я создаю несколько модулей для интеграции проекта с музыкальными сервисами, такими как Spotify, у которых есть API REST. Все эти модули включают в себя еще один модуль mixin, который я создал с именем APIClientBuilder, который предоставляет небольшую DSL для создания конечных точек API.

Библиотека/интеграции/api_client_builder.rb

require 'rest-client' 

module APIClientBuilder 

    attr_accessor :api_client, :endpoint, :url, :param 

    def api_client(api_name) 
    end 

    def fetch_client(api_name) 
    end 

    def api_endpoint(endpoint_name) 
    end 

    def fetch_endpoint(api_name,endpoint_name) 
    end 

    def method=(meth) 
    end 

    def url=(endpoint_url) 
    end 

    def param(param_name,param_value) 
    end 

    def call(api_name,api_endpoint,token,*extra_params) 
    end 

end 

Библиотека/интеграции/spotify.rb

require_relative 'api_client_builder' 

module SpotifyIntegration 

    include APIClientBuilder 

    def base_url 
    'https://api.spotify.com/v1' 
    end 

    def random_state_string 
    (0..10).map { (65 + rand(26)).chr }.join 
    end 

    api_client('spotify') do |apic| 

    apic.api_endpoint('request_authorization') do |ep| 
     ep.method = :get 
     ep.url = "https://accounts.spotify.com/authorize" 
     ep.param("client_id",SPOTIFY_KEY) 
     ep.param("response_type","code") 
     ep.param("redirect_uri","http://localhost:3000") 
    end 

    apic.api_endpoint('my_playlists') do |ep| 
     ep.method = :get 
     ep.url = "#{base_url}/me/playlists" 
    end 

    end 

end 

Моя идея была, имея в своих контроллерах что-то вроде этого:

приложение/контроллеры/api/v1/users_controller.rb

require 'integrations/spotify.rb' 

class UsersController < ApplicationController 

    include SpotifyIntegration 

end 

А затем получить доступ к методам в SpotifyIntegration и, через это, к методам в APIClientBuilder.

Случается, что я написал следующую спецификацию файла с очень простой тест:

спецификации/Библиотека/интеграций/spotify_integration_spec.rb

require 'rails_helper' 

require 'integrations/spotify' 

class SpotifyClientTester 
    include SpotifyIntegration 
end 

RSpec.describe SpotifyIntegration do 

    context 'Auxiliary methods' do 

    it 'Two calls to random_state_string shall generate two different strings' do 
     obj = SpotifyClientTester.new 
     s1 = obj.random_state_string 
     s2 = obj.random_state_string 
     expect(s1).not_to eq(s2) 
    end 

    end 

end 

Но когда я запускаю его я получаю

не определена локальная переменная или метод base_url для SpotifyIntegration: Module (NameError)

Я не уверен, что мне не хватает. Может быть, я должен использовать extend вместо include. Я всегда смущаюсь по этому поводу.

Может ли кто-нибудь поставить меня на правильный путь? Я боролся с этой ошибкой целый день.

+0

Какая линия не работает? Вы даже не вызываете метод 'base_url' в тесте, а' random_state_string' не вызывает 'base_url'. – archana

+0

'lib/integration/spotify.rb: 33', в котором говорится' ep.url = "# {base_url}/me/playlists" '. Вы упомянули, что меня больше всего смущает. Я даже не называю 'base_url'. –

+0

Это не в отрывке в этом посте, 'ep.url =" # {base_url}/me/playlists ". – archana

ответ

3

Вы злоупотребляя Примеси. Используйте mixins для случаев, когда классическое наследование не подходит для добавления набора объектов к объектам.

Например:

module Commentable 
    extend ActiveSupport::Concern 

    included do 
    has_many :comments, as: :commentable 
    end 
    # ... 
end 

class Video < ApplicationRecord 
    include Commentable 
end 

class Hotel < ApplicationRecord 
    include Commentable 
end 

Как вы можете видеть на этом примере вы extend модуль с другими модулями и модулями include в классах. Использование классического наследования для добавления общего поведения было бы неудобно в лучшем случае, поскольку два класса - яблоки и груши.

В вашем конкретном случае вместо этого вы должны использовать классическое наследование, а не смешивать клиент API с контроллером. Скорее, контроллер должен вызывать его как отдельный объект.

class APIClient 
    # Implement shared behavior for a REST api client 
end 

class SpotifyClient < APIClient 
    # ... 
end 

class FoosController < ApplicationController 
    def index 
    client = SpotifyClient.new 
    @foos = client.get_something 
    end 
end 

Почему вы не должны смешивать клиент API с контроллером или моделью? Из-за принципа единой ответственности и того факта, что использование небольших частей, которые делают ограниченное количество вещей, предпочтительнее создания божественных классов.

+0

Да, клиент может быть модулем, если вы не собираетесь создавать его экземпляры. Но в большинстве случаев вы хотите настроить клиентов и сервисные объекты как классы, чтобы упростить настройку с помощью инициализатора. – max

1

Необходимо использовать APIClientBuilder, если вы хотите использовать методы, определенные здесь, на уровне класса в модуле SpotifyIntegration.

module SpotifyIntegration 

    extend APIClientBuilder 

Кроме того, base_url должен быть метод класса тоже def self.base_url

+0

Расширить его в 'SpotifyIntegration'? –