<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Yaroslav Shmarov</title>
    <description>The latest articles on Forem by Yaroslav Shmarov (@superails).</description>
    <link>https://forem.com/superails</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F40122%2F737a7bbc-8b2b-4153-a3c1-9c20c27e8d9d.jpg</url>
      <title>Forem: Yaroslav Shmarov</title>
      <link>https://forem.com/superails</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/superails"/>
    <language>en</language>
    <item>
      <title>TailwindCSS on Rails: Minimize Collapsible Sidebar</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Sun, 30 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://forem.com/superails/tailwindcss-on-rails-minimize-collapsible-sidebar-44ag</link>
      <guid>https://forem.com/superails/tailwindcss-on-rails-minimize-collapsible-sidebar-44ag</guid>
      <description>&lt;p&gt;A ruby friend named Daniel emailed me a request for this feature:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8D9_82jT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.corsego.com/assets/images/tailwind-sidebar-request.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8D9_82jT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.corsego.com/assets/images/tailwind-sidebar-request.png" alt="email request for this blogpost" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s my solution, Daniel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AnGFFXOF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/taiwlind-sidebar-toggle-save.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AnGFFXOF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/taiwlind-sidebar-toggle-save.gif" alt="Tailwind collapsible sidebar" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Collapsible sidebar&lt;/li&gt;
&lt;li&gt;✅ Save state&lt;/li&gt;
&lt;li&gt;✅ Elegant solution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, create a stimulus controller to collapse sidebar. Write the current state to cookies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/javascript/controllers/sidebar_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["sidebarContainer"];

  toggle(e) {
    e.preventDefault();
    this.switchCurrentState();
  }

  switchCurrentState() {
    const newState = this.element.dataset.expanded === "true" ? "false" : "true";
    this.element.dataset.expanded = newState;
    document.cookie = `sidebar_expanded=${newState}`;
    // document.cookie = `sidebar_expanded=${newState}; path=/`;
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The toggle can be triggered by a button like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= button_to "Toggle", nil, data: { action: "click-&amp;gt;sidebar#toggle" }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Toggling the button will update &lt;code&gt;cookies[:sidebar_expanded]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is accessible in CSS via &lt;code&gt;[[data-expanded=false]_&amp;amp;]:&lt;/code&gt;. You can use it as a &lt;strong&gt;condition&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Here’s a sidebar that hides text like &lt;code&gt;"Home"&lt;/code&gt; &amp;amp; &lt;code&gt;"Buttons"&lt;/code&gt; when &lt;code&gt;data-expanded=false&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- app/views/layouts/_sidebar.html.erb --&amp;gt;
&amp;lt;nav class="bg-slate-400 hidden md:flex flex-col text-center p-4 justify-between sticky top-20 h-[calc(100vh-80px)]" data-controller="sidebar" data-expanded="&amp;lt;%= (cookies[:sidebar_expanded] || true) %&amp;gt;"&amp;gt;
  &amp;lt;div class="flex flex-col text-left"&amp;gt;
    &amp;lt;%= link_to root_path do %&amp;gt;
      &amp;lt;span&amp;gt;🏠&amp;lt;/span&amp;gt;
      &amp;lt;span class="[[data-expanded=false]_&amp;amp;]:hidden"&amp;gt;Home&amp;lt;/span&amp;gt;
    &amp;lt;% end %&amp;gt;
    &amp;lt;%= link_to buttons_path do %&amp;gt;
      &amp;lt;span&amp;gt;🔘&amp;lt;/span&amp;gt;
      &amp;lt;span class="[[data-expanded=false]_&amp;amp;]:hidden"&amp;gt;Buttons&amp;lt;/span&amp;gt;
    &amp;lt;% end %&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="text-left"&amp;gt;
    &amp;lt;%= button_to nil, data: { action: "click-&amp;gt;sidebar#toggle" } do %&amp;gt;
      &amp;lt;span class="[[data-expanded=true]_&amp;amp;]:hidden"&amp;gt;➡️&amp;lt;/span&amp;gt;
      &amp;lt;span class="[[data-expanded=false]_&amp;amp;]:hidden"&amp;gt;⬅️&amp;lt;/span&amp;gt;
      &amp;lt;span class="[[data-expanded=false]_&amp;amp;]:hidden"&amp;gt;Toggle&amp;lt;/span&amp;gt;
    &amp;lt;% end %&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🤠 Voila!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Embedded Stripe Checkout</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Wed, 24 Apr 2024 00:00:00 +0000</pubDate>
      <link>https://forem.com/superails/embedded-stripe-checkout-2aig</link>
      <guid>https://forem.com/superails/embedded-stripe-checkout-2aig</guid>
      <description>&lt;p&gt;😖 I really dislike &lt;a href="https://stripe.com/en-bg/payments/elements"&gt;Stripe Elements&lt;/a&gt;: you have to build and style the checkout form yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lcln9IG_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.corsego.com/assets/images/stripe-checkout-elements.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lcln9IG_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.corsego.com/assets/images/stripe-checkout-elements.png" alt="stripe-checkout-elements.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🤩 Instead, &lt;a href="https://stripe.com/en-bg/payments/checkout"&gt;stripe-hosted full screen checkout&lt;/a&gt; is a much better approach where you outsource all the payment logic to Stripe. It looks great:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dr7YLg82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/stripe-checkout-hosted.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dr7YLg82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/stripe-checkout-hosted.gif" alt="stripe-checkout-hosted.gif" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Heres a &lt;a href="https://github.com/corsego/rails-7-stripe-subscriptions/commits/main/"&gt;Github Repo&lt;/a&gt; where I have it implemented.&lt;/p&gt;

&lt;p&gt;Stripe Hosted Checkout on SupeRails:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uUuaXKlP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/stripe-checkout-hosted-superails.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uUuaXKlP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/stripe-checkout-hosted-superails.gif" alt="stripe-checkout-hosted-superails.gif" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🥰🥰 But now you can use &lt;a href="https://docs.stripe.com/payments/accept-a-payment?platform=web&amp;amp;ui=embedded-form"&gt;Embedded Stripe Checkout&lt;/a&gt; &lt;strong&gt;inside&lt;/strong&gt; your app and keep your branding!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--skeapTlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/stripe-checkout-embedded-superails.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--skeapTlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/stripe-checkout-embedded-superails.gif" alt="stripe-checkout-embedded-superails.gif" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Here’s how you can implement Embedded Stripe Checkout:
&lt;/h3&gt;

&lt;p&gt;Create a pricing page that redirects to the checkout page&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/routes.rb
  get "pricing", to: "stripe/checkout#pricing"
  get "checkout/new", to: "stripe/checkout#checkout", as: "new_checkout"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.stripe.com/checkout/embedded/quickstart"&gt;Stripe Embedded Checkout API&lt;/a&gt; has &lt;code&gt;ui_mode: :embedded&lt;/code&gt; and &lt;code&gt;return_url&lt;/code&gt; params that you should set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app/controllers/stripe/checkout_controller.rb
  # GET
  def pricing
    lookup_key = %w[month year lifetime]
    @prices = Stripe::Price.list(lookup_keys: lookup_key, active: true,
                                 expand: ['data.product']).data.sort_by(&amp;amp;:unit_amount)
  end

  # GET
  def checkout
    price = Stripe::Price.retrieve(params[:price_id])
    @session = Stripe::Checkout::Session.create({
                                                  customer: current_user.stripe_customer_id,
                                                  # allow_promotion_codes: true,
                                                  # automatic_tax: {enabled: @plan.taxed?},
                                                  # consent_collection: {terms_of_service: :required},
                                                  # customer_update: {address: :auto},
                                                  # payment_method_collection: :if_required,
                                                  line_items: [{
                                                    price:,
                                                    quantity: 1
                                                  }],
                                                  mode: mode(price),
                                                  return_url: user_url(current_user),
                                                  ui_mode: :embedded
                                                })
  end

  private

  MODES = {
    'recurring' =&amp;gt; 'subscription',
    'one_time' =&amp;gt; 'payment',
    'setup' =&amp;gt; 'setup'
  }.freeze

  def mode(price)
    MODES[price.type]
  end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Display links to checkout for each different price:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app/views/stripe/checkout/pricing.html.erb
&amp;lt;% @prices.each do |price| %&amp;gt;
  &amp;lt;%= link_to "Checkout" new_checkout_path(price_id: price.id) %&amp;gt;
&amp;lt;% end %&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will redirect to the checkout page. You will need some JS to embed the Stripe Checkout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app/views/stripe/checkout/checkout.html.erb
&amp;lt;%= javascript_include_tag "https://js.stripe.com/v3/" %&amp;gt;

&amp;lt;%= tag.div data: {
  controller: "stripe--embedded-checkout",
  stripe__embedded_checkout_public_key_value: Rails.application.credentials.dig(Rails.env, :stripe, :public),
  stripe__embedded_checkout_client_secret_value: @session.client_secret
} %&amp;gt;


rails g stimulus stripe/embedded_checkout


// app/javascript/controllers/stripe/embedded_checkout_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static values = {
    publicKey: String,
    clientSecret: String,
  }

  async connect() {
    this.stripe = Stripe(this.publicKeyValue)
    this.checkout = await this.stripe.initEmbeddedCheckout({clientSecret: this.clientSecretValue})
    this.checkout.mount(this.element)
  }

  disconnect() {
    this.checkout.destroy()
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! Now you have the latest, coolest Stripe Checkout!&lt;/p&gt;

&lt;p&gt;Don’t forget to also add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webhooks to create customers, handle successful and failed payments, subscription state changes&lt;/li&gt;
&lt;li&gt;Billing portal for user to manage plan, change payment methods, see invoice history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See examples here: &lt;a href="https://github.com/corsego/rails-7-stripe-subscriptions/commits/main/"&gt;github.com/corsego/rails-7-stripe-subscriptions&lt;/a&gt;&lt;/p&gt;

</description>
      <category>stripe</category>
    </item>
    <item>
      <title>Manage active sessions in Rails 2024</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Sun, 24 Mar 2024 00:00:00 +0000</pubDate>
      <link>https://forem.com/superails/manage-active-sessions-in-rails-2024-5apl</link>
      <guid>https://forem.com/superails/manage-active-sessions-in-rails-2024-5apl</guid>
      <description>&lt;p&gt;Ok, so sometimes to enchance security of your application you will want to allow users to see all the devices/browsers they are logged in with. You would also provided a button to sign out of a device/browser.&lt;/p&gt;

&lt;p&gt;Here’s how you can do it:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Store current login (device/browser) info and ensure the current device/browser has not been logged out.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g resource Login user:references device_id ip_address user_agent


# app/models/user.rb
class User &amp;lt; ApplicationRecord
  has_many :logins, dependent: :destroy
end


# app/models/login.rb
class Login &amp;lt; ApplicationRecord
  belongs_to :user
end


# app/controllers/application_controller.rb
  include Users::LoginActivity

  # ensure this device has not been logged out
  before_action :validate_login, if: :current_user

  # this works with Devise
  # when logging a user in, register the current device
  def after_sign_in_path_for(resource_or_scope)
    set_login # this!
    root_path
  end


# app/controllers/concerns/users/login_activity.rb
module Users
  module LoginActivity
    extend ActiveSupport::Concern

    # included do
    # helper_method :set_login
    # end

    def set_login
      current_login = current_user.logins.where(device_id: device_id).first_or_create(ip_address: request.remote_ip, user_agent: request.user_agent, device_id: device_id)
      # current_login.touch
      # current_login.save
      session[:device_id] = device_id
      # cookies[:device_id] = device_id
    end

    def validate_login
      if Rails.env.test?
        # mock
        current_login = current_user.logins.create(device_id: "test_device_id")
      else
        current_login ||= current_user.logins.find_by(device_id: session[:device_id])
      end

      if current_login.nil?
        sign_out current_user

        flash[:notice] = 'Device not recognized'
        redirect_to new_user_session_path
      end
    end

    private

    def device_id
      Digest::SHA256.hexdigest("#{request.remote_ip}_#{request.user_agent}")
      # SecureRandom.hex
    end

  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Views to see all logins of a user, log out of a select device/browser
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/routes.rb
  namespace :users do
    resources :logins, only: %i[index destroy]
  end


# app/controllers/users/logins_controller.rb
class Users::LoginsController &amp;lt; Accounts::BaseController
  def index
    @logins = current_user.logins
  end

  def destroy
    @login = current_user.logins.find(params[:id])
    @login.destroy

    flash[:notice] = "You have been logged out of this device."
    redirect_to edit_user_registration_path
  end
end


# app/views/logins/index.html.erb
&amp;lt;% @logins.order(updated_at: :desc).each do |login| %&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;i class="&amp;lt;%= device_type_icon(login.user_agent) %&amp;gt;"&amp;gt;&amp;lt;/i&amp;gt;

    &amp;lt;%= device_description(login.user_agent) %&amp;gt;

    Last login at:
    &amp;lt;%= login.updated_at %&amp;gt;

    IP address:
    &amp;lt;%= login.ip_address %&amp;gt;

    &amp;lt;% if login.device_id == session[:device_id] %&amp;gt;
      current session
    &amp;lt;% else %&amp;gt;
      &amp;lt;%= link_to 'Disconnect', users_login_path(login), method: :delete %&amp;gt;
    &amp;lt;% end %&amp;gt;

  &amp;lt;/div&amp;gt;
&amp;lt;% end %&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ &lt;a href="https://github.com/podigee/device_detector"&gt;gem “device_detector”&lt;/a&gt; will help you decript &lt;code&gt;user_agent&lt;/code&gt; info and make it user-friendly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module LoginsHelper
  def device_description(user_agent)
    device = DeviceDetector.new(user_agent)
    "#{device.name} / #{device.os_name}"
  end

  def device_type_icon(user_agent)
    device = DeviceDetector.new(user_agent)

    case device.device_type
    when "tablet"
      "fa fa-tablet"
    when "smartphone"
      "fa fa-phone"
    when "desktop"
      "fa fa-desktop"
    end
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! 🤗&lt;/p&gt;

&lt;p&gt;Inspired by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vitobotta.com/2016/10/19/manage-active-sessions-"&gt;vitobotta - Manage active sessions in Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adamcooke/authie"&gt;adamcooke/authie&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>pentest</category>
    </item>
    <item>
      <title>Turbo 8 Prefetch (InstantClick)</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Tue, 19 Mar 2024 00:00:00 +0000</pubDate>
      <link>https://forem.com/superails/turbo-8-prefetch-instantclick-3mb6</link>
      <guid>https://forem.com/superails/turbo-8-prefetch-instantclick-3mb6</guid>
      <description>&lt;p&gt;Inspired by &lt;a href="http://instantclick.io/"&gt;instaclick.io&lt;/a&gt;, instaclick &lt;a href="https://github.com/hotwired/turbo/pull/1101"&gt;has been added&lt;/a&gt; as a default behaviour in Turbo 8.&lt;/p&gt;

&lt;p&gt;InstantClick makes an assumption about potential user behaviour: now whenever you &lt;strong&gt;hover&lt;/strong&gt; on a link, it will fire a GET request to retrieve that page. So when you actually click on the link, it will load faster.&lt;/p&gt;

&lt;p&gt;InstantClick/prefetch example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tQxj_ONL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/turbo-8-prefetch-intantclick.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tQxj_ONL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/turbo-8-prefetch-intantclick.gif" alt="turbo-8-prefetch-intantclick" width="800" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Such a request will also have a header &lt;code&gt;X-Sec-Purpose: prefetch&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here are a few tips based on &lt;a href="https://github.com/hotwired/turbo/pull/1101/files#diff-9c52929162118b63d4b92d7cfdd942a11cbb266179248b2500d5cadc8c42bfd5"&gt;the docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These links will never be prefetched:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# current page
link_to "Comments", request.path
# anchor tags (obviously on current page)
link_to "Comments", "#comments"
# non-GET requests
link_to "Upvote", upvote_post_path(@post), data: {turbo_method: :post}
# other websites
link_to "Other website", "https://superails.com"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disable prefetch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;link_to "Admin", admin_path, data: {turbo_prefetch: false}
link_to "Admin", admin_path, data: {turbo: false}

# disable for a block
&amp;lt;div data-turbo="false"&amp;gt;
  link_to "Admin", admin_path
&amp;lt;/div&amp;gt;

# disable globally in application.html.erb
&amp;lt;meta name="turbo-prefetch" content="true" /&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wrong assumptions can lead to more server load&lt;/li&gt;
&lt;li&gt;you would not want to count these prefetch requests as page visits&lt;/li&gt;
&lt;li&gt;whenever you re-hover on a link, it will trigger yet another request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MSqeo-2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/turbo-8-instantclick-prefetch-weird-revisits.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MSqeo-2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.corsego.com/assets/images/turbo-8-instantclick-prefetch-weird-revisits.gif" alt="turbo-8-instantclick-prefetch-weird-revisits" width="800" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>turborails</category>
      <category>prefetch</category>
      <category>instantclick</category>
    </item>
    <item>
      <title>Generate PDF with Ferrum (headless Chrome API)</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Sat, 27 Jan 2024 00:00:00 +0000</pubDate>
      <link>https://forem.com/superails/generate-pdf-with-ferrum-headless-chrome-api-1e9k</link>
      <guid>https://forem.com/superails/generate-pdf-with-ferrum-headless-chrome-api-1e9k</guid>
      <description>&lt;p&gt;Thanks to URLBOX for sponsoring this blogpost! Convert URL to PNG/PDF with &lt;a href="https://urlbox.com/screenshot-api"&gt;UrlBox screenshot API&lt;/a&gt;. Urlbox also helps to block Ads &amp;amp; Popups, Bypass Captchas, Auto-Accept Cookies.&lt;/p&gt;




&lt;p&gt;Usually I would use an HTML-to-PDF library to to generate and display a PDF.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An absolutely different&lt;/strong&gt; , alternative way to view or download a page as PDF would be via a &lt;strong&gt;headless browser API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rubycdp/ferrum"&gt;Gem Ferrum&lt;/a&gt; lets you open a headless chrome browser and perform different actions (click link, take screenshot, open as PDF, etc.)&lt;/p&gt;

&lt;p&gt;Why not use Ferrum to open or save a file as PDF?&lt;/p&gt;

&lt;p&gt;Here’s how it could work:&lt;/p&gt;

&lt;p&gt;Example of an HTML page ⤵️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H2grW3_O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.corsego.com/assets/images/ferrum-page-html.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H2grW3_O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.corsego.com/assets/images/ferrum-page-html.png" alt="Ferrum HTML page" width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking the &lt;em&gt;“View as PDF”&lt;/em&gt; link would open a link as PDF ⤵️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NB9eytCo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.corsego.com/assets/images/ferrum-page-pdf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NB9eytCo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.corsego.com/assets/images/ferrum-page-pdf.png" alt="Ferrum page turned into PDF" width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Install and use &lt;a href="https://github.com/rubycdp/ferrum"&gt;Gem Ferrum&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Install the gem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Gemfile
gem "ferrum"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/routes.rb
get 'home/index'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add an HTML page.&lt;/p&gt;

&lt;p&gt;You can add a &lt;code&gt;link_to&lt;/code&gt; render the page as PDF.&lt;/p&gt;

&lt;p&gt;Notice I add a class &lt;code&gt;.no-print&lt;/code&gt;, so that the link does not get displayed in &lt;code&gt;print&lt;/code&gt; media type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- app/views/home/index.html.erb --&amp;gt;
&amp;lt;h1&amp;gt;Home#index&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;Find me in app/views/home/index.html.erb&amp;lt;/p&amp;gt;
&amp;lt;div class="no-print"&amp;gt;
  &amp;lt;%= link_to "View as PDF", home_index_path(format: :pdf) %&amp;gt;
&amp;lt;/div&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;.no-print&lt;/code&gt; CSS class to elements that should not be printable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* app/assets/stylesheets/application.css */
@media print {
  .no-print {
    display: none !important;
  }
}

/* other css classes you might want to add */
@media print {
  body {
    -webkit-print-color-adjust: exact;
    background: #fff;
    background-color: #fff;
    float: none;
    display: block;
  }
  .no_margin {
    padding-left: 0px !important;
  }
  .no-print {
    display: none !important;
  }
  .printer-preview-content,
  .printer-preview-content_landscape {
    max-width: 100% !important;
    width: 100% !important;
    height: auto !important;
    padding: 0 !important;
    margin: 0 !important;
  }
  .cover_div {
    display: table;
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, use Ferrum to navigate to an internal or external URL and display it as PDF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app/controllers/home_controller.rb
class HomeController &amp;lt; ApplicationController
  def index
    respond_to do |format|
      format.html
      format.pdf do
        handle_pdf_format
      end
    end
  end

  private

  def handle_pdf_format
    filename = controller_name
    tmp = Tempfile.new("pdf-chrome-#{filename}")
    browser = Ferrum::Browser.new
    # browser.go_to("http://localhost:3000/home/index")
    browser.go_to(home_index_url)
    # browser.go_to("https://google.com")
    # click_on_text "Accept all"
    # browser.at_css(".QS5gu.sy4vM").click
    sleep(0.3)
    browser.pdf(
      path: tmp.path,
      format: "A4".to_sym,
      landscape: false,
      # margin: {top: 36, right: 36, bottom: 36, left: 36},
      # preferCSSPageSize: true,
      # printBackground: true
    )
    browser.quit
    pdf_data = File.read(tmp.path)
    pdf_filename = "#{filename}.pdf"
    send_data(pdf_data,
              filename: pdf_filename,
              type: "application/pdf",
              disposition: "inline")
  ensure
    tmp.close
    tmp.unlink
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works well for publicly accessible URLs, however it is not so straightforwar for links that require &lt;code&gt;current_user&lt;/code&gt; authentication. I’ve asked a question here &lt;a href="https://github.com/rubycdp/ferrum/issues/423"&gt;https://github.com/rubycdp/ferrum/issues/423&lt;/a&gt; and am hoping for an easy enough solution 🤠&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ferrum</category>
      <category>pdf</category>
      <category>htmltopdf</category>
    </item>
    <item>
      <title>Send SMS with Twilio in Rails</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Sun, 26 Feb 2023 00:26:55 +0000</pubDate>
      <link>https://forem.com/superails/send-sms-with-twilio-in-rails-3bg8</link>
      <guid>https://forem.com/superails/send-sms-with-twilio-in-rails-3bg8</guid>
      <description>&lt;p&gt;Over the years SMS has become a much less important way of &lt;strong&gt;communication&lt;/strong&gt; , but it can still be useful for receiving important &lt;strong&gt;system notifications&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Common reasons to be using SMS in 2023:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity verification (confirm phone number via SMS)&lt;/li&gt;
&lt;li&gt;Important appointment reminders (doctors appointment)&lt;/li&gt;
&lt;li&gt;2 Factor Authentication via SMS (my grandma does not use 2FA apps)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, when I was creating a chatgpt account, it required a confirmed phone number. Most likely to decrease the amount of bots:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fsms-verify-chatgpt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fsms-verify-chatgpt.png" alt="sms-verify-chatgpt.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twilio.com/docs/libraries/ruby" rel="noopener noreferrer"&gt;&lt;strong&gt;Twilio&lt;/strong&gt;&lt;/a&gt; offers a very easy way to send SMS using Rails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Twilio API keys
&lt;/h3&gt;

&lt;p&gt;After creating an account, navigate to &lt;a href="https://console.twilio.com" rel="noopener noreferrer"&gt;https://console.twilio.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fsms-twilio-api-keys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fsms-twilio-api-keys.png" alt="sms-twilio-api-keys.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save twilio credentials in your apps &lt;code&gt;credentials.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;twilio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;account_sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AC714f90d7750d7cf2&lt;/span&gt;
  &lt;span class="na"&gt;auth_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;c8671c5e8233b0&lt;/span&gt;
  &lt;span class="na"&gt;from_phone_number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+18305801212"&lt;/span&gt;
  &lt;span class="na"&gt;dummy_to_phone_number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+48537623523"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you send messages while in trial mode, you must first verify your ‘To’ phone number (&lt;code&gt;dummy_to_phone_number&lt;/code&gt;). Ensure that your phone number is &lt;a href="https://console.twilio.com/?frameUrl=/console/sms/settings/geo-permissions" rel="noopener noreferrer"&gt;in a valid geo&lt;/a&gt; &amp;amp; &lt;a href="https://console.twilio.com/us1/develop/phone-numbers/manage/verified" rel="noopener noreferrer"&gt;is verified&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Send SMS with Twilio in a Rails app
&lt;/h3&gt;

&lt;p&gt;Install &lt;a href="https://github.com/twilio/twilio-ruby" rel="noopener noreferrer"&gt;&lt;code&gt;gem 'twilio-ruby'&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a Service that you can call in your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/twilio/send_sms_service.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Twilio&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendSmsService&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'twilio-ruby'&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;account_sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:settings&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:account_sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;auth_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:settings&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:auth_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;from_phone_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:settings&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:from_phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;dummy_to_phone_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:settings&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:dummy_to_phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="vi"&gt;@client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Twilio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;REST&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="n"&gt;account_sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth_token&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                           &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt;
                           &lt;span class="ss"&gt;from: &lt;/span&gt;&lt;span class="n"&gt;from_phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_phone_number&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                           &lt;span class="c1"&gt;# media_url: ['https://demo.twilio.com/owl.png'] # MMS example&lt;/span&gt;
                          &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sid&lt;/span&gt;
      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Twilio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;REST&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TwilioError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dummy_to_phone_number&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;development?&lt;/span&gt;

      &lt;span class="n"&gt;to_phone_number&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the service and send an SMS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'some text'&lt;/span&gt;
&lt;span class="n"&gt;to_phone_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'+38050554470367'&lt;/span&gt;
&lt;span class="no"&gt;Twilio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SendSmsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing with Rspec
&lt;/h3&gt;

&lt;p&gt;Assuming &lt;code&gt;from_phone_number = 18305803384&lt;/code&gt;, the test and stubbed Webmock request could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/services/send_sms_service_spec.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Twilio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SendSmsService&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'lorem ipsum'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:to_phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'+123456789'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:twilio_api_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"https://api.twilio.com/2010-04-01/Accounts/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:settings&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:account_sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Messages.json"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;stub_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;twilio_api_url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'calls twilio api'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'sends request'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;call&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;WebMock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_requested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;twilio_api_url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s1"&gt;'Body=lorem+ipsum&amp;amp;From=%2B18305803384&amp;amp;To=%2B123456789'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final thoughts, next steps
&lt;/h3&gt;

&lt;p&gt;Ideally, before sending transactional SMS messages, I would build a mechanism for the user to verify his phone number.&lt;/p&gt;

&lt;p&gt;Here’s how it would work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User inputs his phone number.&lt;/li&gt;
&lt;li&gt;We generate random 6-digit token and send it to the user via SMS.&lt;/li&gt;
&lt;li&gt;User receives SMS, submits token in a form.&lt;/li&gt;
&lt;li&gt;If the token that the user submits is correct, we mark his phone number as verified.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also, we might be able to track if an SMS has been delivered on Twilio’s side. Than we would mark the phone number as invalid if the SMS is not deliverable.&lt;/p&gt;

&lt;p&gt;We can also try to validate the to_phone_number via Twilio lookup API.&lt;/p&gt;

&lt;p&gt;It all depends on your unique usecase and how far your are willing to go :)&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>twilio</category>
    </item>
    <item>
      <title>Live Visit Count for website or page. ActionCable, Turbo Broadcasts, Kredis</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Wed, 15 Feb 2023 22:50:10 +0000</pubDate>
      <link>https://forem.com/superails/live-visit-count-for-website-or-page-actioncable-turbo-broadcasts-kredis-17i2</link>
      <guid>https://forem.com/superails/live-visit-count-for-website-or-page-actioncable-turbo-broadcasts-kredis-17i2</guid>
      <description>&lt;p&gt;It’s amazing that now you can add advanced javascript/websockets features like “Live visitor count” without writing a single extra line of javascript!&lt;/p&gt;

&lt;p&gt;A “live visitor count” feature can give users a “you are not alone” feeling. For example, when watching a live stream on youtube:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fyoutube-watching-now-count.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fyoutube-watching-now-count.png" alt="youtube-watching-now-count.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another example - see other people that have the same google doc open:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fgoogle-docs-users-online.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fgoogle-docs-users-online.png" alt="google-docs-users-online.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“live visitor count” can also boost urgency (booking website example):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fbooking-boost-urgency.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fbooking-boost-urgency.png" alt="booking-boost-urgency.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the end of this article you will have created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;live counter of all visitors on the app&lt;/li&gt;
&lt;li&gt;live conter of all visitors on a specific page or “inside a Room” (like google doc or youtube live video)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fturbo-broadcasts-action-cable.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fturbo-broadcasts-action-cable.gif" alt="turbo-broadcasts-action-cable.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Explanation of the above GIF:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I have 2 browsers open. Each browser = different session.&lt;/li&gt;
&lt;li&gt;When I open the website in the second browser, the visitor count goes from &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the user closes the browser tab, visitor count will decrease by &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When I open a specific &lt;code&gt;Room&lt;/code&gt; page, the visitor count goes from &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt; for this particular Room.&lt;/li&gt;
&lt;li&gt;If a user leaves the Room (closes tab or redirects), visitor count will decrease by &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technologies we will need to implement the “live visitor count”:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://guides.rubyonrails.org/action_cable_overview.html" rel="noopener noreferrer"&gt;ActionCable&lt;/a&gt; - the core technology, that lets us to establish a websockets connection, and see if a user &lt;em&gt;connected&lt;/em&gt; or &lt;em&gt;disconnected&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hotwired/turbo-rails/blob/main/app/channels/turbo/streams_channel.rb" rel="noopener noreferrer"&gt;turbo/streams_channel.rb&lt;/a&gt; - a way to link a turbo stream with an ActionCable channel.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rails/kredis#installation" rel="noopener noreferrer"&gt;gem Kredis&lt;/a&gt; - a Redis adapter for Rails, where we will store the visitor ids (&lt;code&gt;session_id&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Count all current visitors on your website
&lt;/h3&gt;

&lt;p&gt;In this example we will not implement user authentication. We will identify separate users by &lt;strong&gt;sessions&lt;/strong&gt;. One browser = one session.&lt;/p&gt;

&lt;p&gt;To make &lt;code&gt;session_id&lt;/code&gt; available within an ActionCable Channel, you have to add it inside Connection. The Connection has access to request and cookie data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/channels/application_cable/connection.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ApplicationCable&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;identified_by&lt;/span&gt; &lt;span class="ss"&gt;:session_user&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add a &lt;code&gt;turbo_stream_from&lt;/code&gt; broadcast to your application layout.&lt;/p&gt;

&lt;p&gt;Importantly, append the &lt;code&gt;channel: VisitorChannel&lt;/code&gt; to connect to an ActionCable channel.&lt;/p&gt;

&lt;p&gt;Add a placeholder div &lt;code&gt;visitors-counter&lt;/code&gt; where we will update the number of current visitors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/views/layouts/application.html.erb&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= turbo_stream_from :visitors, channel: VisitorChannel %&amp;gt;

Current website visitors: 
&amp;lt;span id=&lt;/span&gt;&lt;span class="s2"&gt;"visitors-counter"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create an action cable channel. Don’t use the generator, because you will not need the other extra files and code that &lt;code&gt;rails g channel visitor&lt;/code&gt; would give you. It has to inherit from &lt;code&gt;Turbo::StreamsChannel&lt;/code&gt;. Add &lt;code&gt;super&lt;/code&gt;. That way you will have &lt;code&gt;streams_form&lt;/code&gt; automatically imported. You will have access to &lt;code&gt;verified_stream_name_from_params&lt;/code&gt; that is &lt;code&gt;:visitors&lt;/code&gt; for our current case.&lt;/p&gt;

&lt;p&gt;In this case &lt;code&gt;verified_stream_name_from_params&lt;/code&gt; = &lt;code&gt;:visitors&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a redis unique list and &lt;strong&gt;add&lt;/strong&gt; the session_user on &lt;code&gt;subscribed&lt;/code&gt;, remove the user on &lt;code&gt;unsubscribed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Send a turbo stream broadcast to update this count in everyones views with &lt;code&gt;Turbo::StreamsChannel.broadcast_update_to&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/channels/visitor_channel.rb&lt;/span&gt;
&lt;span class="c1"&gt;# class VisitorChannel &amp;lt; ApplicationCable::Channel&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VisitorChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamsChannel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;
    &lt;span class="n"&gt;visitors_online&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Kredis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique_list&lt;/span&gt; &lt;span class="s2"&gt;"visitors_online"&lt;/span&gt;
    &lt;span class="n"&gt;visitors_online&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;session_user&lt;/span&gt;
    &lt;span class="n"&gt;update_visitors_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visitors_online&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unsubscribed&lt;/span&gt;
    &lt;span class="n"&gt;visitors_online&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Kredis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique_list&lt;/span&gt; &lt;span class="s2"&gt;"visitors_online"&lt;/span&gt;
    &lt;span class="n"&gt;visitors_online&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt; &lt;span class="n"&gt;session_user&lt;/span&gt;
    &lt;span class="n"&gt;update_visitors_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visitors_online&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_visitors_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visitors_online&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamsChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_update_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;verified_stream_name_from_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s1"&gt;'visitors-counter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="n"&gt;visitors_online&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! Now every visitor of your application will see the visitor count!&lt;/p&gt;

&lt;p&gt;Try opening/closing pages in your app from 2 different browsers and see the &lt;code&gt;visitor_count&lt;/code&gt; change.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Count current visitors on a specific page
&lt;/h3&gt;

&lt;p&gt;Maybe a more useful feature would be to see the quantity of users on a specific page. For example, we have a scaffold of &lt;code&gt;Room&lt;/code&gt; and each room has a &lt;code&gt;#show&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;In this case we will pass an additional &lt;code&gt;room_id&lt;/code&gt; param to the &lt;code&gt;turbo_stream_from&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Make the &lt;code&gt;target&lt;/code&gt; also unique based on the &lt;code&gt;room_id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/views/rooms/show.html.erb&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= turbo_stream_from @room, channel: RoomChannel, params: { room_id: @room.id } %&amp;gt;
&amp;lt;%#=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_from&lt;/span&gt; &lt;span class="vi"&gt;@room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;channel: &lt;/span&gt;&lt;span class="no"&gt;RoomChannel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;room_id: &lt;/span&gt;&lt;span class="vi"&gt;@room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="no"&gt;People&lt;/span&gt; &lt;span class="n"&gt;inside&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="ss"&gt;room: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"room-&amp;lt;%= params[:room_id]%&amp;gt;-counter"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can access &lt;code&gt;params[:room_id]&lt;/code&gt; from our &lt;code&gt;room_channel.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/channels/room_channel.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoomChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamsChannel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;
    &lt;span class="n"&gt;room_visitors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Kredis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique_list&lt;/span&gt; &lt;span class="s2"&gt;"room_visitors"&lt;/span&gt;
    &lt;span class="n"&gt;room_visitors&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;session_user&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"room-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:room_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-counter"&lt;/span&gt;
    &lt;span class="n"&gt;update_visitors_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;room_visitors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unsubscribed&lt;/span&gt;
    &lt;span class="n"&gt;room_visitors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Kredis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique_list&lt;/span&gt; &lt;span class="s2"&gt;"room_visitors"&lt;/span&gt;
    &lt;span class="n"&gt;room_visitors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt; &lt;span class="n"&gt;session_user&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"room-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:room_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-counter"&lt;/span&gt;
    &lt;span class="n"&gt;update_visitors_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;room_visitors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_visitors_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;room_visitors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamsChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_update_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;verified_stream_name_from_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt;
      &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="n"&gt;room_visitors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! Now when you open a specific room page, it will register a new connection. See the “People inside this room” counter get updated:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fturbo-broadcasts-action-cable.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fturbo-broadcasts-action-cable.gif" alt="turbo-broadcasts-action-cable.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it. See you in the next one!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>kredis</category>
      <category>actioncable</category>
    </item>
    <item>
      <title>Omniauth without Devise</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Tue, 07 Feb 2023 23:59:00 +0000</pubDate>
      <link>https://forem.com/superails/omniauth-without-devise-26mc</link>
      <guid>https://forem.com/superails/omniauth-without-devise-26mc</guid>
      <description>&lt;p&gt;Previously I’ve covered &lt;a href="https://blog.corsego.com/devise-omniauth-github" rel="noopener noreferrer"&gt;Github omniauth with Devise&lt;/a&gt;, and &lt;a href="https://blog.corsego.com/devise-login-only-with-omniauth" rel="noopener noreferrer"&gt;Github omniauth with Devise without email registration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An even &lt;strong&gt;simpler&lt;/strong&gt; solution would be to sign in via a social login provider &lt;strong&gt;without Devise&lt;/strong&gt; at all! Here’s the easiest way to do it.&lt;/p&gt;

&lt;p&gt;First, add the &lt;a href="https://github.com/omniauth/omniauth" rel="noopener noreferrer"&gt;omniauth&lt;/a&gt; gems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'omniauth-github'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;github: &lt;/span&gt;&lt;span class="s1"&gt;'omniauth/omniauth-github'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;branch: &lt;/span&gt;&lt;span class="s1"&gt;'master'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"omniauth-rails_csrf_protection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your social provider API credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# https://github.com/omniauth/omniauth&lt;/span&gt;
&lt;span class="c1"&gt;# https://github.com/settings/applications/new&lt;/span&gt;
&lt;span class="c1"&gt;# echo &amp;gt; config/initializers/omniauth.rb&lt;/span&gt;
&lt;span class="c1"&gt;# config/initializers/omniauth.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;OmniAuth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Builder&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="ss"&gt;:github&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"GITHUB_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"GITHUB_SECRET"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a user model. We will also add a few static pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/landing_page&lt;/code&gt; that can be accessed without authentication&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/dashboard&lt;/code&gt; that requires authentication
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="n"&gt;static_pages&lt;/span&gt; &lt;span class="n"&gt;landing_page&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="n"&gt;github_uid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="s1"&gt;'static_pages#landing_page'&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'dashboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'static_pages#dashboard'&lt;/span&gt;

  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'auth/github/callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'sessions#create'&lt;/span&gt;
  &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="s1"&gt;'logout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'sessions#destroy'&lt;/span&gt;
  &lt;span class="c1"&gt;# get 'login', to: redirect('/auth/github'), as: 'login'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gems like devise provide some default methods, that we will have to add on our own now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;def current_user&lt;/code&gt; - get the current user from session params.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;def user_signed_in?&lt;/code&gt; - check if there is a current user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;def require_authentication&lt;/code&gt; - to restrict controller actions for non-authenticated users.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;helper_method :current_user&lt;/code&gt; - to make &lt;code&gt;current_user&lt;/code&gt; available in views.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/application_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;protect_from_forgery&lt;/span&gt; &lt;span class="ss"&gt;with: :exception&lt;/span&gt;
  &lt;span class="n"&gt;helper_method&lt;/span&gt; &lt;span class="ss"&gt;:current_user&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;require_authentication&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;alert: &lt;/span&gt;&lt;span class="s1"&gt;'Requires authentication'&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;user_signed_in?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;current_user&lt;/span&gt;
    &lt;span class="vi"&gt;@current_user&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_signed_in?&lt;/span&gt;
    &lt;span class="c1"&gt;# converts current_user to a boolean by negating the negation&lt;/span&gt;
    &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The button to &lt;code&gt;/auth/github&lt;/code&gt; will redirect to the github login page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/views/layouts/application.html.erb&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= link_to 'Home', root_path %&amp;gt;
&amp;lt;% if current_user %&amp;gt;
  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;
  &amp;lt;%= link_to 'Dashboard', dashboard_path %&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= button_to 'Logout', logout_path, method: :delete, data: { turbo: false } %&amp;gt;
&amp;lt;% else %&amp;gt;
  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;button_to&lt;/span&gt; &lt;span class="s2"&gt;"Sign in with Github"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/auth/github"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;turbo: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After successful authentication, the user should be redirected to &lt;code&gt;sessions#create&lt;/code&gt; with &lt;code&gt;request.env['omniauth.auth']&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/sessions_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_omniauth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'omniauth.auth'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted?&lt;/span&gt;
      &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;dashboard_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s2"&gt;"Logged in as &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;alert: &lt;/span&gt;&lt;span class="s1"&gt;'Failure'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;from_omniauth&lt;/code&gt; will find the users &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;uid&lt;/code&gt; in the data provided by github, and find or create the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/user.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:github_uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;uniqueness: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MailTo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EMAIL_REGEXP&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;uniqueness: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_omniauth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;github_uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uid&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_or_create_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;github_uid&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, require authentication to visit &lt;code&gt;/dashboard&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/static_pages_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StaticPagesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:require_authentication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: :dashboard&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;landing_page&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dashboard&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! Now you can use omniauth without devise!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>devise</category>
      <category>omniauth</category>
      <category>github</category>
    </item>
    <item>
      <title>How to use the Browser Geolocation API with StimulusJS and Rails</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Sun, 05 Feb 2023 19:23:41 +0000</pubDate>
      <link>https://forem.com/superails/how-to-use-the-browser-geolocation-api-with-stimulusjs-and-rails-14c7</link>
      <guid>https://forem.com/superails/how-to-use-the-browser-geolocation-api-with-stimulusjs-and-rails-14c7</guid>
      <description>&lt;p&gt;Task: get users &lt;strong&gt;coordinates&lt;/strong&gt; from browser, redirect to page with coordinates in params:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fgeolocation-api-search.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fgeolocation-api-search.gif" alt="geolocation-api-search.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To request users’ location, we will use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition" rel="noopener noreferrer"&gt;Web/API/Geolocation/getCurrentPosition&lt;/a&gt; with StimulusJS.&lt;/p&gt;

&lt;p&gt;Create stimulus controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;stimulus&lt;/span&gt; &lt;span class="n"&gt;geolocation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/controllers/geolocation_controller.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hotwired/stimulus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;enableHighAccuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maximumAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Connects to data-controller="geolocation"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geolocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// redirect with coordinates in params&lt;/span&gt;
    &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/locations/?place=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;crd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;crd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`ERROR(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;): &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, a button to get location:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"geolocation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"geolocation#search"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;search near me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 Interestingly, if you use &lt;code&gt;this.success&lt;/code&gt; instead of &lt;code&gt;this.success.bind(this)&lt;/code&gt;, stimulus targets will not work within the success function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get address from coordinates
&lt;/h3&gt;

&lt;p&gt;Get &lt;strong&gt;address&lt;/strong&gt; (city, street...) based on &lt;strong&gt;coordinates&lt;/strong&gt; using &lt;a href="https://blog.corsego.com/gem-geocoder-ruby" rel="noopener noreferrer"&gt;&lt;strong&gt;geocoder&lt;/strong&gt;&lt;/a&gt; , and search &lt;code&gt;near&lt;/code&gt; the address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;# app/javascript/controllers/geolocation_controller.js
&lt;span class="gd"&gt;- location.assign(`/locations/?place=${crd.latitude},${crd.longitude}`)
&lt;/span&gt;&lt;span class="gi"&gt;+ location.assign(`/locations/?coordinates=${crd.latitude},${crd.longitude}`)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;# app/controllers/locations_controller.rb
  def index
&lt;span class="gi"&gt;+    if params[:coordinates].present?
+      place = Geocoder.search(params[:coordinates])
+      params[:place] = place.first.address
+    end
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    if params[:place].present?
      @locations = Location.near(params[:place], params[:distance] || 10, order: :distance)
    else
      @locations = Location.all
    end
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! Now you can get Geolocation with Javascript and use it within a Rails app!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>geolocation</category>
      <category>stimulus</category>
    </item>
    <item>
      <title>gem MapkickJS for beautiful JavaScript maps with one line of Ruby</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Thu, 02 Feb 2023 19:28:28 +0000</pubDate>
      <link>https://forem.com/superails/gem-mapkickjs-for-beautiful-javascript-maps-with-one-line-of-ruby-k6a</link>
      <guid>https://forem.com/superails/gem-mapkickjs-for-beautiful-javascript-maps-with-one-line-of-ruby-k6a</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/ankane/mapkick.js" rel="noopener noreferrer"&gt;MapkickJS&lt;/a&gt; is a javascript adapter to display coordinates on Mapbox maps. It requires a &lt;a href="https://account.mapbox.com/auth/signup/" rel="noopener noreferrer"&gt;Mapbox API key&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ankane/mapkick" rel="noopener noreferrer"&gt;mapkick-rb&lt;/a&gt; is a Ruby on Rails adapter for MapkickJS. It allows you to easily feed a JSON with &lt;strong&gt;coordinates&lt;/strong&gt; and display a map within your Rails app.&lt;/p&gt;

&lt;p&gt;To display a marker on a map, you need to know &lt;strong&gt;latitude&lt;/strong&gt; and &lt;strong&gt;longitude&lt;/strong&gt; GPS coordinates. &lt;a href="https://dev.to%7B%20post_url%202023-01-22-gem-geocoder-ruby%20%7D"&gt;gem Geocoder&lt;/a&gt; allows you to get &lt;strong&gt;coordinates&lt;/strong&gt; based on an &lt;strong&gt;address&lt;/strong&gt; (house, street, city, state, country).&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic usage
&lt;/h3&gt;

&lt;p&gt;After installing the gem, initialize your Mapkick API key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# echo &amp;gt; config/initializers/mapbox.rb&lt;/span&gt;
&lt;span class="c1"&gt;# config/initializers/mapbox.rb&lt;/span&gt;
&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"MAPBOX_ACCESS_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pk.eyJ1..."&lt;/span&gt;
&lt;span class="c1"&gt;# ENV["MAPBOX_ACCESS_TOKEN"] = Rails.application.credentials.dig(:mapkick_api_key)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basic map with multiple options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= js_map [{latitude: 37.7829,
             longitude: -122.4190,
             label: 'My home',
             tooltip: 'Hello!'
            }],
            id: "cities-map",
            width: "800px",
            height: "500px",
            markers: {color: "#00FF00"},
            tooltips: { hover: false, html: true},
            style: "mapbox://styles/mapbox/outdoors-v12",
            zoom: 15,
            controls: true,
            refresh: 60 %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fmapbox-map-all-params.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fmapbox-map-all-params.png" alt="mapbox-map-all-params" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML tooltips
&lt;/h3&gt;

&lt;p&gt;Map with clickable links to locations:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fmapbox-map-clickable-link.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fmapbox-map-clickable-link.png" alt="mapbox-map-clickable-link" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a helper with a link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/helpers/locations_helper.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;LocationsHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;html_link_to_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;location_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s1"&gt;'_blank'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;style: &lt;/span&gt;&lt;span class="s1"&gt;'font-weight: bold; color: green'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be able to click on the tooltip, use the option &lt;code&gt;{ hover: false, html: true}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Render the helper method in the tooltip param:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= js_map [{latitude: location.latitude,
             longitude: location.longitude,
             label: location.name,
             tooltip: html_link_to_location(location)}],
             tooltips: { hover: false, html: true} %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Display multiple locations on the map, JSON
&lt;/h3&gt;

&lt;p&gt;For this, the best way will be to render &lt;code&gt;/locations.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= js_map locations_path(format: :json) %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Customize the JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;app/views/locations/_location.json.jbuilder&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;json.extract!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;location,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:latitude,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:longitude&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;json.label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;location.name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;json.tooltip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;html_link_to_location(location)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;json.tooltip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#{html_link_to_location(location)} &amp;lt;br&amp;gt; #{location.address}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fmapbox-map-multiple-locations.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fmapbox-map-multiple-locations.png" alt="mapbox-map-multiple-locations" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  With search params
&lt;/h3&gt;

&lt;p&gt;In this final example, we will factor in having a search form for &lt;code&gt;place&lt;/code&gt; and &lt;code&gt;distance&lt;/code&gt;. The maps’ &lt;code&gt;zoom&lt;/code&gt; will be smaller/higher based on the maximum search &lt;code&gt;distance&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/locations_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocationsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="sx"&gt;%i[show edit update destroy]&lt;/span&gt;

  &lt;span class="c1"&gt;# GET /locations or /locations.json&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:place&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="vi"&gt;@locations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;near&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:place&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:distance&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order: :distance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# this!&lt;/span&gt;
      &lt;span class="vi"&gt;@zoom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:distance&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;eql?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'10'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="vi"&gt;@locations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be sure to add the query params to the path in the view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= js_map locations_path(format: :json, place: params[:place], distance: params[:distance]), zoom: @zoom %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result - smaller/bigger map zoom based on search params:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fgeocoder-search-zoom.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.corsego.com%2Fassets%2Fimages%2Fgeocoder-search-zoom.gif" alt="geocoder-search-zoom" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it! Now you can build your own AIRNBN search frontend!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>mapkick</category>
    </item>
    <item>
      <title>gem Geocoder - calculate coordinates, distances, search nearby</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Tue, 31 Jan 2023 22:54:16 +0000</pubDate>
      <link>https://forem.com/superails/gem-geocoder-calculate-coordinates-distances-search-nearby-7h9</link>
      <guid>https://forem.com/superails/gem-geocoder-calculate-coordinates-distances-search-nearby-7h9</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/alexreisner/geocoder"&gt;Geocoder gem&lt;/a&gt; allows you to preform different operations with &lt;strong&gt;coordinates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Useful usage examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;find &lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt; coordinates by &lt;code&gt;address&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;address&lt;/code&gt; by &lt;code&gt;lat-lon&lt;/code&gt; coordinates,&lt;/li&gt;
&lt;li&gt;find all Locations within a &lt;strong&gt;square&lt;/strong&gt; (&lt;code&gt;within_bounding_box&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;find the &lt;strong&gt;distance&lt;/strong&gt; between two Locations (&lt;code&gt;distance_from&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;find all Locations around X &lt;strong&gt;coordinates&lt;/strong&gt; (&lt;code&gt;near&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;find all Locations around X &lt;strong&gt;location&lt;/strong&gt; (&lt;code&gt;nearbys&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Afterwards, when you have the coordinates of a location, you can use a &lt;strong&gt;separate&lt;/strong&gt; Maps API to display them on a map.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic geocoder usage
&lt;/h3&gt;

&lt;p&gt;Geocoder gem does a search via a Places API, and returns coordinates. The more detailed address you search for, the more precise the coordinates you will receive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;geocoder&lt;/span&gt;

&lt;span class="c1"&gt;# search&lt;/span&gt;
&lt;span class="n"&gt;ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Geocoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Kyiv'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;coordinates&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [50.4500336, 30.5241361] # latitude and longitude&lt;/span&gt;
&lt;span class="n"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;country&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Ukraine'&lt;/span&gt;

&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Geocoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Notre dame cathedral paris'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;city&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Paris'&lt;/span&gt;

&lt;span class="c1"&gt;# geographic_center&lt;/span&gt;
&lt;span class="no"&gt;Geocoder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Calculations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geographic_center&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [50.51223060957045, 16.201193185230583]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Debug current HTTP request
&lt;/h3&gt;

&lt;p&gt;You can get the current web requests country/ip/etc.&lt;/p&gt;

&lt;p&gt;You can use it, for example, to &lt;a href="https://dev.to/block-russian-ips"&gt;geoblock countries like Ruzzia&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# controller or view&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;location&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;location&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# request.ip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Geocode a Rails model
&lt;/h3&gt;

&lt;p&gt;Storing the address as a single string looks like a simple straightforward solution, however storing each address detail separately gives you more power.&lt;/p&gt;

&lt;p&gt;A usual address on Google Maps has the sequence &lt;code&gt;street, city, state, country, zip&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Scaffold your location model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;scaffold&lt;/span&gt; &lt;span class="no"&gt;Location&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="ss"&gt;:float:index&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="ss"&gt;:float:index&lt;/span&gt; &lt;span class="n"&gt;street&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="n"&gt;zip&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;scaffold&lt;/span&gt; &lt;span class="no"&gt;Location&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="ss"&gt;:float:index&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="ss"&gt;:float:index&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="ss"&gt;:migrate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Geocoder will automatically perform a search and find the &lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt; of your location.&lt;/p&gt;

&lt;p&gt;To save compute power and API thresholds, it makes sence to geocode only if the address has changed.&lt;/p&gt;

&lt;p&gt;Basic (one &lt;code&gt;address&lt;/code&gt; field):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/location.rb&lt;/span&gt;
  &lt;span class="n"&gt;geocoded_by&lt;/span&gt; &lt;span class="ss"&gt;:address&lt;/span&gt;   
  &lt;span class="n"&gt;after_validation&lt;/span&gt; &lt;span class="ss"&gt;:geocode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if: :address_changed?&lt;/span&gt;
  &lt;span class="c1"&gt;# after_validation :geocode, if: -&amp;gt;(obj){ obj.address.present? and obj.address_changed? }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Advanced (multiple &lt;code&gt;address&lt;/code&gt; fields):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/location.rb&lt;/span&gt;
  &lt;span class="n"&gt;geocoded_by&lt;/span&gt; &lt;span class="ss"&gt;:address&lt;/span&gt;   
  &lt;span class="n"&gt;after_validation&lt;/span&gt; &lt;span class="ss"&gt;:geocode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if: :address_changed?&lt;/span&gt; 

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;address&lt;/span&gt; 
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;address_changed?&lt;/span&gt;
    &lt;span class="n"&gt;country_changed?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="n"&gt;state_changed?&lt;/span&gt; 
      &lt;span class="n"&gt;city_changed?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="n"&gt;street_changed?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="n"&gt;zip_changed?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DEMO DATA&lt;/strong&gt; : seeds with a few real hotels in France&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/seeds.rb&lt;/span&gt;
&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hôtel Martinez - The Unbound Collection by Hyatt"&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"73 Bd de la Croisette, 06400 Cannes"&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;

&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Exclusive Hotel Belle Plage"&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2 Rue Brougham, 06400 Cannes"&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;

&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Best Western Premier Le Patio des Artistes - Cannes"&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"6 Rue de Bône, 06400 Cannes"&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;

&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Le Negresco"&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"37 Prom. des Anglais, 06000 Nice"&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;

&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Caesars Palace"&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3570 S Las Vegas Blvd, Las Vegas, NV 89109, United States"&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can find call geocoder methods on the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# geocode a single record:&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geocode&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;

&lt;span class="c1"&gt;# geocode all:&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geocode&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geocoded&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; return objects with coordinates&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not_geocoded&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; return objects without coordinates&lt;/span&gt;

&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_coordinates&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [51.51436195, 31.31593525714063]&lt;/span&gt;

&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nearbys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nearbys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;units: :km&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; array of locations within 20 km of coordinates, excluding selected location&lt;/span&gt;
&lt;span class="c1"&gt;# ! useful to show "similar" or "nearby" feature&lt;/span&gt;

&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;near&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;units: :km&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order: :distance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;near&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Omaha, NE, US'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; all locations within 20 km of coordinates&lt;/span&gt;
&lt;span class="c1"&gt;# ! useful for "find all next to...." feature&lt;/span&gt;

&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distance_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distance_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# same as above&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distance_form&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;40.714&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;100.234&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 1.8493403104012456&lt;/span&gt;

&lt;span class="c1"&gt;# all locations within square&lt;/span&gt;
&lt;span class="n"&gt;sw_corner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;40.71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;100.23&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="n"&gt;ne_corner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;36.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;88.65&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;within_bounding_box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sw_corner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ne_corner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Search locations near
&lt;/h3&gt;

&lt;p&gt;Having a search for for &lt;code&gt;place&lt;/code&gt; and &lt;code&gt;distance&lt;/code&gt;, you can find relevant Locations. This can be a vital feature when building a website like AirBnB or Booking.com.&lt;/p&gt;

&lt;p&gt;Example query in human words: &lt;em&gt;Find all locations within 10km distance from Chernihiv, Ukraine&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/locations_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocationsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:place&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="vi"&gt;@locations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;near&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:place&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:distance&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order: :distance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="vi"&gt;@locations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;add a search form within a view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= form_with url: locations_path, method: :get do |form| %&amp;gt;
  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:place&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"City, Country"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= form.text_field :place, value: params[:place] %&amp;gt;

  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Distance, km"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= text_field_tag :distance, [10, 20, 30], params[:distance] %&amp;gt;

  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="s2"&gt;"Search"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Display coordinates on static map
&lt;/h3&gt;

&lt;p&gt;To display a market on a static image map, you would need to connect a places API.&lt;/p&gt;

&lt;p&gt;There are a few options for using Places API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.mapbox.com/playground/static/"&gt;🔥🔥 &lt;strong&gt;Mapbox maps&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/documentation/maps-static"&gt;🔥 &lt;strong&gt;google maps&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/bingmaps/rest-services/imagery/get-a-static-map#get-a-road-map-with-building-footprints"&gt;❓ &lt;strong&gt;Bing maps&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.here.com/"&gt;❓ &lt;strong&gt;HERE maps&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the above, I’ve tried only Mapbox. As long as you receive a Mapbox API key, you can display the map with an &lt;code&gt;image_tag&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= image_tag "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/pin-s+ff2700(#{location.longitude},#{location.latitude})/#{location.longitude},#{location.latitude},13,0/300x200?access_token=&lt;/span&gt;&lt;span class="c1"&gt;#{Rails.application.credentials.dig(:mapbox_key)}" %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s much more that we can do with coordinates. In the future I hope to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gem Mapkick for displaying multiple Locations on a responsive map&lt;/li&gt;
&lt;li&gt;different Geocoding API adapters (like Amazon Location API)&lt;/li&gt;
&lt;li&gt;get browser location with Javascript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it for now.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>geocoder</category>
    </item>
    <item>
      <title>Parse a YML/YAML file in a Ruby on Rails app</title>
      <dc:creator>Yaroslav Shmarov</dc:creator>
      <pubDate>Mon, 30 Jan 2023 18:00:25 +0000</pubDate>
      <link>https://forem.com/superails/parse-a-ymlyaml-file-in-a-ruby-on-rails-app-5h7i</link>
      <guid>https://forem.com/superails/parse-a-ymlyaml-file-in-a-ruby-on-rails-app-5h7i</guid>
      <description>&lt;p&gt;Instead of storing &lt;strong&gt;static data&lt;/strong&gt; a database, you can create a structured YML or JSON file and parse it from your Ruby/Rails app.&lt;/p&gt;

&lt;p&gt;I really like the data structure of a &lt;code&gt;.yml&lt;/code&gt; file, because it is much cleaner to write than &lt;code&gt;.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here’s an example list of schools in the YAML format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/view_data/schools.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Austin High School&lt;/span&gt;
  &lt;span class="na"&gt;team_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Austin Mustangs&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Houston&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TX&lt;/span&gt;
  &lt;span class="na"&gt;primary_color&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;green&lt;/span&gt;
  &lt;span class="na"&gt;secondary_color&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;white&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bellaire High School&lt;/span&gt;
  &lt;span class="na"&gt;team_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bellaire Cardinals&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Houston&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TX&lt;/span&gt;
  &lt;span class="na"&gt;primary_color&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;red-lighter&lt;/span&gt;
  &lt;span class="na"&gt;secondary_color&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;white&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Carnegie Vanguard High School&lt;/span&gt;
  &lt;span class="na"&gt;team_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Carnegie Vanguard Rhinos&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Houston&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TX&lt;/span&gt;
  &lt;span class="na"&gt;primary_color&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blue&lt;/span&gt;
  &lt;span class="na"&gt;secondary_color&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;red-lighter&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can parse this data (convert it into a Hash or Array) using Ruby on Rails &lt;strong&gt;native yaml parsers&lt;/strong&gt;!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse a local YAML file with pure &lt;strong&gt;Ruby&lt;/strong&gt; :
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'yaml'&lt;/span&gt;
&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/Users/yaroslavshmarov/Documents/GitHub.nosync/schools.yml"&lt;/span&gt;
&lt;span class="vi"&gt;@schools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="vi"&gt;@schools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Austin High School"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source: &lt;a href="https://ruby-doc.org/stdlib-2.5.1/libdoc/yaml/rdoc/YAML.html" rel="noopener noreferrer"&gt;Ruby YAML docs&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse a YAML file inside a &lt;strong&gt;Rails&lt;/strong&gt; app:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# a controller action&lt;/span&gt;
  &lt;span class="c1"&gt;# @schools = YAML::load File.open("#{Rails.root.to_s}/db/fixtures/schools.yml") # ruby way&lt;/span&gt;
  &lt;span class="vi"&gt;@schools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'db/fixtures/schools.yml'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# rails way&lt;/span&gt;
  &lt;span class="vi"&gt;@schools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Render the results in a view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# a view&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% @schools.each &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;school&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;
  &amp;lt;%= school.fetch('name') %&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= school['name'] %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source: &lt;a href="https://apidock.com/ruby/YAML/load_file/class" rel="noopener noreferrer"&gt;Rails YAML.load_file docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it! 🤠&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>yaml</category>
      <category>yml</category>
    </item>
  </channel>
</rss>
