diff --git a/content/guides/set-up-rails-appplication-with-omniauth-bookingsync.md b/content/guides/set-up-rails-appplication-with-omniauth-bookingsync.md
new file mode 100644
index 00000000..45d482da
--- /dev/null
+++ b/content/guides/set-up-rails-appplication-with-omniauth-bookingsync.md
@@ -0,0 +1,303 @@
+# Building New Rails Application With Devise And BookingSync Integration
+
+If you need to integrate your existing Rails application (which most likely uses Devise for authentication) with BookingSync, that's the exact guide you are looking for.
+
+## Setup
+
+After setting up environment for Ruby (check either [rvm](https://rvm.io) or [rbenv](https://github.com/rbenv/rbenv)), install `bundler` and `rails` gems:
+
+~~~
+gem install bundler
+gem install rails
+~~~
+
+And generate new Rails application:
+
+~~~
+rails new omniauth-bookingsync-app
+cd omniauth-bookingsync-app
+~~~
+
+To finish the setup we just need to create the database:
+
+~~~
+bundle exec rake db:create
+~~~
+
+To verify it works, just run:
+
+~~~
+rails server
+~~~
+
+And visit `http://localhost:3000`.
+
+
+## Adding Devise
+
+Add [`devise`](https://github.com/plataformatec/devise) gem to Gemfile:
+
+~~~ Gemfile
+gem 'devise'
+~~~
+
+And run:
+
+~~~
+bundle install
+~~~
+
+Then run the generator:
+
+~~~
+rails generate devise:install
+rails generate devise:views
+~~~
+
+We don't need to have a signup / signin alternative with BookingSync, just a way to integrate the account, so we can remove this part from `app/views/devise/shared/_links.html.erb`:
+
+~~~ html app/views/devise/shared/_links.html.erb
+<%- if devise_mapping.omniauthable? %>
+ <%- resource_class.omniauth_providers.each do |provider| %>
+ <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
+ <%- end -%>
+<% end -%>
+~~~
+
+Another step would be generating a model for Devise, for the sake of simplicity let's call it `Account` to match the concept from BookingSync authentication resource:
+
+~~~
+rails generate devise account
+~~~
+
+The default migration generated by that command should be sufficient for this example app. Now you can run the migration:
+
+~~~
+rake db:migrate
+~~~
+
+## Adding OmniAuth and integrating BookingSync
+
+Go to the [BookingSync Partners section](https://www.bookingsync.com/en/partners/login) and register you account or sign in if you already have one. After you sign in, create a new application. You will need to the provide a name, an admin URL (the URL to which the user should be redirected to after signing in when developing `embedded` application) and a redirect URI. Here's an example:
+
+
++ <%= rental.name %> +
+ <% end %> +<% else %> + <%= link_to "Connect with BookingSync", account_bookingsync_omniauth_authorize_path %> +<% end %> +~~~ + +To make sure it's working correctly we want to display some rentals coming from BookingSync API, we will call them `remote_rentals`. We will also need to add `Account#authorized?` method for checking if this account has already been authorized to access BookingSync API. + +Let's update the routes and add a controller for handling authentication process and OAuth workflow: + +~~~ ruby config/routes.rb +Rails.application.routes.draw do + root to: "admin#index" + + devise_for :accounts, controllers: { omniauth_callbacks: "accounts/omniauth_callbacks" } + + get "admin" => "admin#index" +end +~~~ + +~~~ ruby app/controllers/accounts/omniauth_callbacks_controller.rb +class Accounts::OmniauthCallbacksController < Devise::OmniauthCallbacksController + before_action :authenticate_account! + + def bookingsync + current_account.assign_omniauth_attributes(omniauth_attribues) + + if current_account.save + set_flash_message(:notice, :success, kind: "BookingSync") if is_navigational_format? + redirect_to admin_path + else + redirect_to root_path + end + end + + def failure + redirect_to root_path + end + + private + + def omniauth_attribues + request.env["omniauth.auth"] + end +end +~~~ + +Most of the logic is already handled here by `OmniAuth` and `devise`, so we don't need to do much ourselves. We just need to `Account#assign_omniauth_attributes` method to handle attributes assignment such as `uid`, `name` and token-related attributes. Here's the implementation: + +~~~ app/models/account.rb +class Account < ApplicationRecord + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable and :omniauthable + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :trackable, :validatable + + devise :omniauthable, omniauth_providers: [:bookingsync] + + def assign_omniauth_attributes(auth) + self.uid = auth.uid + self.provider = auth.provides + self.name = auth.info.business_name + update_token(auth.credentials) + end + + def update_token(token) + self.oauth_access_token = token.token + self.oauth_refresh_token = token.refresh_token + self.oauth_expires_at = token.expires_at + end + + def authorized? + oauth_access_token.present? && oauth_refresh_token.present? + end +end +~~~ + +We also added `Account#authorized?` method which checks if the token and refresh token are present. + +That way we have all the logic required for connecting the application's account with BookingSync account. The only thing left is providing the way to fetch rentals. + +## Adding bookingsync-api And Fetching Rentals + +Let's add `bookingsync-api` gem which handles the communication with BookingSync API: + +~~~ +gem "bookingsync-api" +~~~ + +Now we can add some methods to `Account` model: + +~~~ ruby app/models/account.rb +class Account < ApplicationRecord + # some previous methods already covered before go here + + def remote_rentals + bookingsync_api_client.rentals + end + + def bookingsync_api_client + @bookingsync_api_client ||= BookingSync::API.new(oauth_token) + end + + def oauth_token + token.token + end + + def token + @token ||= begin + token_options = {} + if oauth_refresh_token + token_options[:refresh_token] = oauth_refresh_token + token_options[:expires_at] = oauth_expires_at + end + + token = OAuth2::AccessToken.new(oauth_client, oauth_access_token, token_options) + + if token.expired? + refresh_token!(token) + else + token + end + end + end + + def refresh_token!(current_token = token) + @token = current_token.refresh!.tap { |new_token| update_token(new_token) } + save! + @token + end + + def oauth_client + client_options = { + site: "https://www.bookingsync.com", + connection_opts: { headers: { accept: "application/vnd.api+json" } } + } + client_options[:ssl] = { verify: true } + OAuth2::Client.new(ENV["BOOKINGSYNC_APPLICATION_ID"], ENV["BOOKINGSYNC_SECRET"], client_options) + end +end +~~~ + +`Account#remote_rentals` method is responsible for fetching `rentals` from the API and it simply delegates the logic to `bookingsync_api_client`. The API client requires `oauth_token`, which is returned in `token` method. This method also ensures that the token will also be valid - if the token expires, it will perform a proper request using `refresh_token` to get a new one. Fetching tokens requires properly configured `oauth_client` with the right headers and application's data. + +And that's it! Now you can sign in, click the link to connect your account with BookingSync, perform authorization and after redirecting back to you app you should see a list of rentals (if you have created any). diff --git a/layouts/guides.html b/layouts/guides.html index 9cd76174..b4019be7 100644 --- a/layouts/guides.html +++ b/layouts/guides.html @@ -10,6 +10,8 @@ "/guides/create-a-booking-with-a-source", class: "list-group-item") %> <%= link_to_with_current("Read all authorized accounts at once", "/guides/read-all-authorized-accounts-at-once", class: "list-group-item") %> + <%= link_to_with_current("Set Up Rails Application With omniauth-bookingsync", + "/guides/set-up-rails-appplication-with-omniauth-bookingsync", class: "list-group-item") %> <%= link_to_with_current("Getting Started with PHP", "/guides/getting-started-with-php", class: "list-group-item") %> diff --git a/static/images/bookingsync_new_application.png b/static/images/bookingsync_new_application.png new file mode 100644 index 00000000..f33bb836 Binary files /dev/null and b/static/images/bookingsync_new_application.png differ