Yandex Direct API甚のRuby gemを䜜成したす

私はRubyをもっず良く孊びたかったのですが、実際のドラフトはありたせんでした。 そしお、Yandex Direct APIで動䜜するgemを䜜成しようずしたした。


いく぀かの理由がありたした。 その䞭でも、Yandex Direct APIは、Yandexおよび最新のRESTサヌビス党般で非垞に䞀般的です。 兞型的な間違いを理解しお克服すれば、他のYandex APIのアナログを簡単か぀迅速に曞くこずができたすそれだけではありたせん。 それでもなお、私が芋぀けたすべおのアナログは、Directのバヌゞョンのサポヌトに問題がありたしたいく぀かは4の䞋でシャヌプにされ、他は新しい5の䞋でシャヌプにされ、ナニットはどこでもサポヌトしおいたせんでした。


メタプログラミングは玠晎らしいこずです


gemの䞻なアむデアは、RubyやPythonのような蚀語では新しいメ゜ッドずJSONのようなオブゞェクトをその堎で䜜成できるため、RESTサヌビスにアクセスするためのむンタヌフェヌスメ゜ッドはRestサヌビス自䜓の機胜を繰り返すこずができるずいうこずです。 次のように曞くこずができたす


request = { "SelectionCriteria" => { "Types" => ["TEXT_CAMPAIGN"] }, "FieldNames" => ["Id", "Name"], "TextCampaignFieldNames" => ["BiddingStrategy"] } options = { token: Token } @direct = Ya::API::Direct::Client.new(options) json = direct.campaigns.get(request) 

そしお、ヘルプを曞く代わりに、指定されたAPIを䜿甚しおナヌザヌをマニュアルに送りたす。


たずえば、次のような叀いバヌゞョンのメ゜ッドを呌び出したす。


 json = direct.v4.GetCampaignsList 

読むこずに興味はないが、詊しおみたい堎合は、ここから完成した宝石を入手できたす



twitterの䟋から、レヌルから omn​​iauth-tokenを取埗する方法を孊ぶこずができたす。 たた、メ゜ッドの名前ず登録手順は、Yandexのドキュメントで非垞に詳しく説明されおいたす 。


詳现が興味深い堎合-さらに詳现です。


開発を開始


もちろん、この蚘事では最も基本的な経隓ず最も簡単なこずに぀いお説明しおいたす。 しかし、それは兞型的な宝石を䜜成するためのリマむンダヌずしお、初心者私のようなに圹立ちたす。 もちろん、蚘事に関する情報を収集するこずは興味深いですが、長い間です。


最埌に、読者の䞭には、Yandex Direct APIサポヌトをプロゞェクトにすばやく远加する必芁がある堎合がありたす。


たた、フィヌドバックの芳点から、私にずっおも圹立぀でしょう。


テストスクリプト


たず、Yandex Directに登録し、そこでテストアプリケヌションを䜜成し、そのための䞀時トヌクンを取埗したす。


次に、 Yandex Direct APIヘルプを開き、メ゜ッドを呌び出す方法を孊びたす。 このようなもの


バヌゞョン5の堎合


 require "net/http" require "openssl" require "json" Token = "TOKEN" #    TOKEN. def send_api_request_v5(request_data) url = "https://%{api}.direct.yandex.com/json/v5/%{service}" % request_data uri = URI.parse(url) request = Net::HTTP::Post.new(uri.path, initheader = { 'Client-Login' => request_data[:login], 'Accept-Language' => "ru", 'Authorization' => "Bearer #{Token}" }) request.body = { "method" => request_data[:method], "params" => request_data[:params] }.to_json http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE response = http.request(request) if response.kind_of? Net::HTTPSuccess JSON.parse response.body else raise response.inspect end end p send_api_request_v5 api: "api-sandbox", login: "YOUR LOGIN HERE", service: "campaigns", method: "get", params: { "SelectionCriteria" => { "Types" => ["TEXT_CAMPAIGN"] }, "FieldNames" => ["Id", "Name"], "TextCampaignFieldNames" => ["BiddingStrategy"] } 

バヌゞョン4 Liveの堎合トヌクンは䞡方に適しおいたす


 require "net/http" require "openssl" require "json" Token = "TOKEN" #    TOKEN. def send_api_request_v4(request_data) url = "https://%{api}.direct.yandex.com/%{version}/json/" % request_data uri = URI.parse(url) request = Net::HTTP::Post.new(uri.path) request.body = { "method" => request_data[:method], "param" => request_data[:params], "locale" => "ru", "token" => Token }.to_json http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE response = http.request(request) if response.kind_of? Net::HTTPSuccess JSON.parse(response.body) else raise response.inspect end end p send_api_request_v4 api: "api-sandbox", version: "live/v4", method: "GetCampaignsList", params: [] 

これらのスクリプトは、デバッグおよびクむックテストク゚リに既に適しおいたす。


しかし、神話䞊のマン・マンスが教えおいるように、それ自䜓のスクリプトず他の人のラむブラリは、2぀の異なるクラスのアプリケヌションです。 そしお、1぀を別のものに枡すには、汗をかかなければなりたせん。


宝石を䜜成する


そもそも、名前を決定する必芁がありたした-シンプルで忙しくありたせん。 そしお、 ya-api-directが必芁なものであるずいう結論に達したした。


たず、構造自䜓が論理的です。たずえば、ya-api-weatherも衚瀺される堎合、それが䜕を指しおいるかが明確になりたす。 第二に、商暙をプレフィックスずしお䜿甚するYandexの公匏補品がただありたせん。 さらに、これはya.ruのヒントであり、以前の簡朔なデザむンが泚意深く保存されおいたす。


すべおのフォルダヌを手で䜜成するのは少し面倒です。 バンドラヌにこれをさせおください


 bundle gem ya-api-direct 

UnitTestのツヌルずしお、ミニテストを瀺したした。 次に、理由が明らかになりたす。


これでフォルダヌが䜜成され、その䞭にアセンブリgemの準備が敎いたした。 唯䞀の欠点は、完党に空であるこずです。


しかし、今それを修正したす。


テストを曞く


UnitTestは、unningな隠れたバグを怜出するのに非垞に圹立ちたす。 それにもかかわらず、゜ヌスコヌドに隠れおいた方法に沿っおそれらを曞き、数十個のバグを修正するこずができたほずんどすべおのプログラマヌは、圌が垞にそれらを曞くこずを玄束したす。 しかし、圌はただ曞きたせん。


䞀郚のプロゞェクト特に怠け者のプログラマヌによっお曞かれたものは、テストず仕様テストの䞡方を同時に行いたす。 しかし、minitestの最新バヌゞョンでは、突然仕様むンタヌフェむスを孊習し、仕様だけで察凊するこずにしたした。


オンラむンむンタヌフェヌスがあり、さらに、リク゚ストごずにポむントが差し匕かれるので、Yandex Direct APIからの回答を停造したす。 これを行うには、トリッキヌなgem webmockが必芁です 。


宝石に远加


 group :test do gem 'rspec', '>= 2.14' gem 'rubocop', '>= 0.37' gem 'webmock' end 

曎新し、テストフォルダヌの名前をspecに倉曎したす。 急いでいたので、倖郚むンタヌフェむスのみのテストを曞きたした。


 require 'ya/api/direct' require 'minitest/autorun' require 'webmock/minitest' describe Ya::API::Direct::Client do Token = "TOKEN" #  , .. API   . before do @units = { just_used: 10, units_left: 20828, units_limit: 64000 } units_header = {"Units" => "%{just_used}/%{units_left}/%{units_limit}" % @units } @campaigns_get_body = { #     Yandex Direct API    } #    stub_request(:post, "https://api-sandbox.direct.yandex.ru/json/v5/campaigns") .with( headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Accept-Language'=>'en', 'Authorization'=>'Bearer TOKEN', 'Client-Login'=>'', 'User-Agent'=>'Ruby'}, body: {"method" => "get", "params"=> {}}.to_json) .to_return(:status => 200, body: @campaigns_get_body.to_json, headers: units_header) #     @clientV4 = Ya::API::Direct::Client.new(token: Token, api: :v4) @clientV5 = Ya::API::Direct::Client.new(token: Token) end 

webmockは、HTTPを操䜜するための暙準ラむブラリのメ゜ッドを眮き換えたす。これにより、特定のボディずヘッダヌを持぀リク゚ストが返されたずきに、察応するレスポンスが返されたす。


蚭定を間違えたずしおも、倧したこずではありたせん。 フィルタヌにないリク゚ストを送信しようずするず、webmockぱラヌを報告し、スタブを正しく曞く方法を教えおくれたす。


そしお、仕様を曞きたす


 describe "when does a request" do it "works well with version 4" do assert @clientV4.v4.GetCampaignsList == @campaigns_get_body end it "works well with version 5" do assert @clientV5.campaigns.get == @campaigns_get_body end end #    

すくい


Rakeは非垞に柔軟か぀簡単に実装されおいるため、ほがすべおのラむブラリに独自の方法がありたす。 だから私は圌にspec _ *。Rbず呌ばれるすべおのファむルを実行するように圌に蚀った、それはspecディレクトリにある


 require "bundler/gem_tasks" require "rake/testtask" task :spec do Dir.glob('./spec/**/spec_*.rb').each { |file| require file} end task test: [:spec] task default: [:spec] 

これで、仕様を次のように呌び出すこずができたす。


 rake test 

たたは


 rake 

確かに、圌はただテストするものがありたせん。


宝石を曞く


たず、gemに関する情報を入力したすこのバンドルがないず、起動が拒吊されたす。 次に、䜿甚するサヌドパヌティラむブラリをgemspecに曞き蟌みたす。


 gem 'jruby-openssl', platforms: :jruby gem 'rake' gem 'yard' group :test do gem 'rspec', '>= 2.14' gem 'rubocop', '>= 0.37' gem 'webmock' gem 'yardstick' end 

やる


 bundle install 

libに移動しおファむルを䜜成したす。


甚意するファむルは次のずおりです。



異なるバヌゞョンのコントロヌラヌ


最初に、定数を含むファむルを䜜成したす。このファむルに、APIのすべおの関数を曞き蟌みたす。


contants.rb


 module Ya module API module Direct API_V5 = { "Campaigns" => [ "add", "update", "delete", "suspend", "resume", "archive", "unarchive", "get" ], #  .. } API_V4 = [ "GetBalance", #  .. ] API_V4_LIVE = [ "CreateOrUpdateCampaign", #  .. ] end end end 

次に、バヌゞョン4および5のサヌビスを継承する基本的なサヌビスラッパヌを䜜成したす。


direct_service_base.rb


 module Ya::API::Direct class DirectServiceBase attr_reader :method_items, :version def initialize(client, methods_data) @client = client @method_items = methods_data init_methods end protected def init_methods @method_items.each do |method| self.class.send :define_method, method do |params = {}| result = exec_request(method, params || {}) callback_by_result result result[:data] end end end def exec_request(method, request_body) client.gateway.request method, request_body, @version end def callback_by_result(result={}) end end end 

コンストラクタヌで、圌は゜ヌスクラむアントずメ゜ッドのリストを取埗したす。 そしお、define_methodを介しお、それらを䜜成したす。


respond_to_missingメ゜ッドでうたくいかないのはなぜですか 倚くの宝石がただそうであるように 遅いのであたり䟿利ではないからです。 そしお、それなしでは、遅いむンタヌプリタヌが䟋倖の埌にそれに入り、is_respond_to_missing..をチェックむンしたす。さらに、この方法で䜜成されたメ゜ッドはメ゜ッド呌び出しの結果に分類され、デバッグに䟿利です。


次に、バヌゞョン4および4 Liveのサヌビスを䜜成したす。


direct_service_v4.rb


 require "ya/api/direct/constants" require "ya/api/direct/direct_service_base" module Ya::API::Direct class DirectServiceV4 < DirectServiceBase def initialize(client, methods_data, version = :v4) super(client, methods_data) @version = version end def exec_request(method, request_body = {}) @client.gateway.request method, request_body, nil, (API_V4_LIVE.include?(method) ? :v4live : @version) end end end 

バヌゞョン5では、サヌバヌはナヌザヌリク゚ストに応答するだけでなく、最埌のリク゚ストに費やされたポむント数、残っおいるポむント数、珟圚のセッションにあったポむント数も報告したす。 私たちのサヌビスはそれらを分解できるはずですしかし、これをどのように行うかに぀いおはただ曞いおいたせん。 ただし、メむンクラむアントクラスのフィヌルドを曎新する必芁があるこずを事前に瀺したす。


direct_service_v5.rb


 require "ya/api/direct/direct_service_base" module Ya::API::Direct class DirectServiceV5 < DirectServiceBase attr_reader :service, :service_url def initialize(client, service, methods_data) super(client, methods_data) @service = service @service_url = service.downcase @version = :v5 end def exec_request(method, request_body={}) @client.gateway.request method, request_body, @service_url, @version end def callback_by_result(result={}) if result.has_key? :units_data @client.update_units_data result[:units_data] end end end end 

ずころで、謎めいたゲヌトりェむがリク゚ストの呌び出しに関䞎しおいるこずに気づきたしたか


ゲヌトりェむずUrlHelper


Gatewayクラスはリク゚ストを提䟛したす。 スクリプトのほずんどのコヌドはそこに移動したした。
gateway.rb


 require "net/http" require "openssl" require "json" require "ya/api/direct/constants" require "ya/api/direct/url_helper" module Ya::API::Direct class Gateway #    def request(method, params, service = "", version = nil) ver = version || (service.nil? ? :v4 : :v5) url = UrlHelper.direct_api_url @config[:mode], ver, service header = generate_header ver body = generate_body method, params, ver uri = URI.parse url request = Net::HTTP::Post.new(uri.path, initheader = header) request.body = body.to_json http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = @config[:ssl] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE response = http.request(request) if response.kind_of? Net::HTTPSuccess UrlHelper.parse_data response, ver else raise response.inspect end end #     generate_header  generate_body #    ,   end end 

暙準のNet :: HTTPは、熊手のように単玔であるため、関係しおいたす。 ファラデヌからク゚リを送信するこずもできたす。 OmniAuthは既に動䜜したすこれに぀いおは以䞋で説明したすので、アプリケヌションがgemでオヌバヌロヌドされるこずはありたせん。


最埌に、URLを生成し、デヌタを解析し、ナニットを解析する静的関数をUrlHelperに入力したす簡単です。


 require "json" require "ya/api/direct/exception" module Ya::API::Direct RegExUnits = Regexp.new /(\d+)\/(\d+)\/(\d+)/ class UrlHelper def self.direct_api_url(mode = :sandbox, version = :v5, service = "") format = :json protocol = "https" api_prefixes = { sandbox: "api-sandbox", production: "api" } api_prefix = api_prefixes[mode || :sandbox] site = "%{api}.direct.yandex.ru" % {api: api_prefix} api_urls = { v4: { json: '%{protocol}://%{site}/v4/json', soap: '%{protocol}://%{site}/v4/soap', wsdl: '%{protocol}://%{site}/v4/wsdl', }, v4live: { json: '%{protocol}://%{site}/live/v4/json', soap: '%{protocol}://%{site}/live/v4/soap', wsdl: '%{protocol}://%{site}/live/v4/wsdl', }, v5: { json: '%{protocol}://%{site}/json/v5/%{service}', soap: '%{protocol}://%{site}/v5/%{service}', wsdl: '%{protocol}://%{site}/v5/%{service}?wsdl', } } api_urls[version][format] % { protocol: protocol, site: site, service: service } end def self.extract_response_units(response_header) matched = RegExUnits.match response_header["Units"] matched.nil? ? {} : { just_used: matched[1].to_i, units_left: matched[2].to_i, units_limit: matched[3].to_i } end private def self.parse_data(response, ver) response_body = JSON.parse(response.body) validate_response! response_body result = { data: response_body } if [:v5].include? ver result.merge!({ units_data: self.extract_response_units(response) }) end result end def self.validate_response!(response_body) if response_body.has_key? 'error' response_error = response_body['error'] raise Exception.new(response_error['error_detail'], response_error['error_string'], response_error['error_code']) end end end end 

サヌバヌが゚ラヌを返した堎合、そのテキストずずもに䟋倖をスロヌしたす。


コヌドは自明のようで、それはかなり良いこずです。 自明のコヌドは保守が簡単です。


クラむアント


次に、倖郚むンタヌフェむスずやり取りするクラむアントクラス自䜓を蚘述する必芁がありたす。 ほずんどの機胜は既に内郚クラスに移動しおいるため、非垞に短くなりたす。


 require "ya/api/direct/constants" require "ya/api/direct/gateway" require "ya/api/direct/direct_service_v4" require "ya/api/direct/direct_service_v5" require "ya/api/direct/exception" require 'time' module Ya::API::Direct AllowedAPIVersions = [:v5, :v4] class Client attr_reader :cache_timestamp, :units_data, :gateway, :v4, :v5 def initialize(config = {}) @config = { token: nil, app_id: nil, login: '', locale: 'en', mode: :sandbox, format: :json, cache: true, api: :v5, ssl: true }.merge(config) @units_data = { just_used: nil, units_left: nil, units_limit: nil } raise "Token can't be empty" if @config[:token].nil? raise "Allowed Yandex Direct API versions are #{AllowedVersions}" unless AllowedAPIVersions.include? @config[:api] @gateway = Ya::API::Direct::Gateway.new @config init_v4 init_v5 start_cache! if @config[:cache] yield self if block_given? end def update_units_data(units_data = {}) @units_data.merge! units_data end def start_cache! case @config[:api] when :v4 result = @gateway.request("GetChanges", {}, nil, :v4live) timestamp = result[:data]['data']['Timestamp'] when :v5 result = @gateway.request("checkDictionaries", {}, "changes", :v5) timestamp = result[:data]['result']['Timestamp'] update_units_data result[:units_data] end @cache_timestamp = Time.parse(timestamp) @cache_timestamp end private def init_v4 @v4 = DirectServiceV4.new self, (API_V4 + API_V4_LIVE) end def init_v5 @v5 = {} API_V5.each do |service, methods| service_item = DirectServiceV5.new(self, service, methods) service_key = service_item.service_url @v5[service_key] = service_item self.class.send :define_method, service_key do @v5[service_key] end end end end end 

バヌゞョン4のメ゜ッドはv4プロパティに曞き蟌たれ、バヌゞョン5のメ゜ッドは個別のサヌビスによっおグルヌプ化され、既にわかっおいる構築を通じおクラむアントクラスのメ゜ッドになりたす。 さお、client.campaigns.getを呌び出すず、Rubyは最初にclient.campaignsを実行しおから、結果のサヌビスでgetメ゜ッドを呌び出したす。


クラスをdo ... end構文で䜿甚できるように、コンストラクタヌの最埌の甚語が必芁です。


初期化の盎埌に、蚭定で指定されおいる堎合start_cacheを実行しお、キャッシュを有効にするコマンドをAPIに送信したす。 蚭定のバヌゞョンはこれにのみ圱響し、クラスむンスタンスからは䞡方のバヌゞョンのメ゜ッドを呌び出すこずができたす。 受信した日付はcache_timestampプロパティで利甚可胜になりたす。


units_dataプロパティには、Unitsの最新情報が含たれたす。


たた、プロゞェクトには、バヌゞョン蚭定ず䟋倖を含むクラスがありたす。 圌らにはすべおがはっきりしおいるので、蚀うこずはありたせん。 バヌゞョン蚭定を持぀クラスは、プロゞェクトずずもにバンドルによっお生成されたす。


さお、 direct.rbファむルでは、倖郚からナヌザヌに衚瀺されるクラスを指定する必芁がありたす。 私たちの堎合、これはクラむアントクラスのみです。 さらに、バヌゞョンず䟋倖完党に公匏です。


コンパむルしお塗り぀ぶす


gemをコンパむルするには、RubyGems.orgのマニュアルに埓っおください耇雑なこずは䜕もありたせん。 たたはRailsからMountable Engineを適甚したす。


そしお、rubygemsにアップロヌドしたす-突然、このgemは私たちだけでなく圹に立぀かもしれたせん。


Ruby on Railsからトヌクンを取埗する方法


RailsからYandec APIにログむンしおトヌクンを取埗するこずは、開発者にずっお非垞に簡単なこずです...初めおではありたせん。


既に孊んだように、Direct APIにアクセスするにはトヌクンが必芁です。 Yandexのヘルプから 、TwitterやFacebookを含む倚くのサヌビスで䜿甚される叀き良きOAuth2が私たちの前にあるこずがわかりたす。


Rubyには、さたざたなサヌビスのOAuth2実装が継承する叀兞的なgem omn​​iauthがありたす。 すでに実装され、 omn​​iauth-yandex 。 私たちは圌に察凊しようずしたす。


新しいRailsアプリケヌションを䜜成したしょう孊習した埌、䜜業䞭のプロゞェクトに远加したす。 Gemfileに远加


 gem "omniauth-yandex" 

そしお、バンドルむンストヌルを行いたす。


そしお、レヌル甚のOmniauth認蚌をむンストヌルするためのマニュアルを䜿甚したす。 twitterの䟋を次に瀺したす。 それを翻蚳し、語り盎すこずは、それだけの䟡倀はないず思いたす-蚘事は巚倧であるこずが刀明したした。


この蚘事で説明されおいる䟋は、私にずっおはうたくいきたした。 唯䞀の修正は、SQLiteがそれらをサポヌトしないため、Userテヌブルに远加のむンデックスを曞き蟌たなかったこずです。


確かに、この蚘事はトヌクンが隠れおいる堎所を瀺しおいたせん。 しかし、これはたったく秘密ではありたせん。 SessionControllerで取埗できたす


  request.env['omniauth.auth'].credentials.token 

芚えおおいおください-そのような認蚌はすべおトヌクンを再生成したす。 そしお、埌で盎接トヌクンを䜿甚しおスクリプトを䜿甚しようずするず、サヌバヌは叀いスクリプトはもはや適切ではないず蚀いたす。 Yandexアプリケヌションの蚭定に戻り、デバッグコヌルバックURL__ https://oauth.yandex.ru/verification_code__ を再床指定しおから、トヌクンを再生成する必芁がありたす。


さらに良いこずに、デバッグに干枉しないように、静的トヌクン甚に別のアプリケヌションを䜜成したす。


参照資料




Source: https://habr.com/ru/post/J311512/


All Articles