@drawoharaя ❤️ це! << клікни мене 🐛 🫖 🧚
/dangling-rpc-fruit-off-of-your-rails-controllers
опубліковано: 2013-11-05

при роботі з будь-яким додатком Rails, що не є "hello-world", неминуче потрібні невеликі AJAX-допоміжники для спілкування з сервером, щоб виконувати логіку на стороні клієнта. я не маю на увазі API як такий, а радше адхок утиліти, такі як ці:

markdown.change(function(){
  generate_html_on_the_server( markdown.val(), function(html){
  
    preview.html(html);
  
  });
});

або

reservation.change(function(){
  check_availibity_on_the_server( reservation.val(), function(data){
  
    if(!data['available']){
     
     alert('Цей час недоступний - виберіть інший!');
     
    };
  })
});

і т.д.

Ці типи функцій не є частиною серверно-серверного публічного API, це просто співпрацюючі JS/бекенд-ендпоінти, які потрібні для роботи відображення.

Більшість додатків Rails накопичують багато таких функцій, і виникає питання:

“Куди їх помістити?”

У більшості команд буде три або чотири розробники, які називають та організовують ці функції по-різному, забезпечуючи, щоб кодова база перетворилася на безладну макаронинну суміш в найкоротші терміни.

@dojo4 ми абстрагували це за допомогою невеликого шаблону проектування RPC: спочатку ми маємо невеликий DSL контролера, який включений до нашого ApplicationController, що дозволяє декларативні визначення допоміжників JS 'rpc' на основі кожного контролера.

class ApplicationController < ActionController::Base
  include(RPC)
end

ознайомтесь з реалізацією тут: https://gist.github.com/ahoward/7320900

простою англійською мовою цей dsl просто визначив одну дію на контролері, яка мультиплексує, який метод використовувати на основі параметрів, і легкий спосіб визначити їх. він очікує, що всі дії rpc повернуть хеш і повернуть json.

його реалізація зводиться до

  def rpc
    which = params['method']
    
    action = @rpc[which]
    
    result = action.call(params)
    
    render :json => result.to_json
  end

його використання має бути очевидним з коду

class GeoLocationController < ApplicationController
  rpc(:geo_location) do |params|
    geo_location = GeoLocation.geo_locate( params['address'] )
    geo_location.attributes
  end
  
  rpc(:lat_lng) do |params|
   geo_location = GeoLocation.geo_locate( params['address'] )
   { 'lat' => geo_location.lat, 'lng' => geo_location.lng }
  end
end

використання з JS вимагає двох речей: маршруту та невеликого класу JS. спочатку маршрут:

  match ':controller#rpc', :action => 'rpc'
  

далі, JS, також тут -> https://gist.github.com/ahoward/7320900

переглядаючи це, ви побачите, що використання бекенд-дій rpc з JS таке ж просте, як


  var rpc = new RPC(url);

  rpc.call('geo_location', params, callback);


зазвичай, внизу перегляду ми просто створюємо об'єкт rpc для сторінки з відносним URL

<script>
  jQuery(function(){
 
    var rpc = new RPC(<%= raw url_for(:action => :rpc).to_json %>);
    
  
  });
  
</script>

і ми розпочинаємо.

отже, ось воно - простий спосіб утримати ваші допоміжники JS rpc від забруднення ваших контролерів та переглядів різними стратегіями, і ще один приклад того, як наявність брутально послідовних інтерфейсів робить абстракцію можливою та простою.

оновлення: весь підхід можна узагальнити так

class BaseController
  RPC = Hash.new
  def self.rpc(method_name, &block)
    RPC[method_name