Crear aplicaciones de Ruby on Rails con Microsoft Graph
Este tutorial le enseña a crear una aplicación web ruby on rails que use la API de Microsoft Graph para recuperar información de calendario para un usuario.
Sugerencia
Si prefiere descargar el tutorial completado, puede descargarlo de dos maneras.
- Descargue el inicio rápido de Ruby para obtener el código de trabajo en minutos.
- Descargue o clone el GitHub repositorio.
Requisitos previos
Antes de iniciar este tutorial, debe tener instaladas las siguientes herramientas en el equipo de desarrollo.
También debe tener una cuenta personal de Microsoft con un buzón en Outlook.com, o una cuenta de Trabajo o escuela de Microsoft. Si no tienes una cuenta de Microsoft, hay un par de opciones para obtener una cuenta gratuita:
- Puedes suscribirte a una nueva cuenta personal de Microsoft.
- Puedes suscribirte al programa Microsoft 365 desarrolladores para obtener una suscripción Microsoft 365 gratuita.
Nota
Este tutorial se escribió con las siguientes versiones de las herramientas necesarias. Los pasos de esta guía pueden funcionar con otras versiones, pero eso no se ha probado.
- Ruby versión 3.0.1
- SQLite3 versión 3.35.5
- Node.js versión 14.15.0
- Yarn versión 1.22.0
Comentarios
Proporcione cualquier comentario sobre este tutorial en el repositorio GitHub usuario.
Crear una aplicación web de Ruby on Rails
En este ejercicio, usarás Ruby on Rails para crear una aplicación web.
Si aún no tiene rails instalado, puede instalarlo desde la interfaz de línea de comandos (CLI) con el siguiente comando.
gem install rails -v 6.1.3.1
Abre la CLI, navega a un directorio donde tienes derechos para crear archivos y ejecuta el siguiente comando para crear una nueva aplicación Rails.
rails new graph-tutorial
Navegue a este nuevo directorio y escriba el siguiente comando para iniciar un servidor web local.
rails server
Abra el explorador y vaya a
http://localhost:3000
. Si todo funciona, verá un "Yay! You're on Rails!" Mensaje. Si no ve ese mensaje, consulte la guía de introducción a Rails.
Instalar gemas
Antes de seguir adelante, instala algunas gemas adicionales que usarás más adelante:
- omniauth-oauth2 para controlar los flujos de tokens de inicio de sesión y OAuth.
- omniauth-rails_csrf_protection para agregar protección CSRF a OmniAuth.
- httparty para realizar llamadas a Microsoft Graph.
- activerecord-session_store para almacenar sesiones en la base de datos.
Abra ./Gemfile y agregue las siguientes líneas.
# OAuth gem 'omniauth-oauth2', '~> 1.7.1' # OmniAuth CSRF protection gem 'omniauth-rails_csrf_protection', '~> 1.0.0' # REST calls to Microsoft Graph gem 'httparty', '~> 0.18.1' # Session storage in database gem 'activerecord-session_store', '~> 2.0.0'
En la CLI, ejecute el siguiente comando.
bundle install
En la CLI, ejecute los siguientes comandos para configurar la base de datos para almacenar sesiones.
rails generate active_record:session_migration rake db:migrate
Cree un nuevo archivo llamado
session_store.rb
en el directorio ./config/initializers y agregue el siguiente código.Rails.application.config.session_store :active_record_store, :key => '_graph_app_session'
Diseñar la aplicación
En esta sección, crearás la interfaz de usuario básica para la aplicación.
Abra ./app/views/layouts/application.html.erb y reemplace su contenido por lo siguiente.
<!DOCTYPE html> <html> <head> <title>Ruby Graph Tutorial</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <link rel="shortcut icon" href="favicon.png"/> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous"> <link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/11.0.0/css/fabric.min.css"/> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> </head> <body> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <div class="container"> <%= link_to "Ruby Graph Tutorial", root_path, class: "navbar-brand" %> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarCollapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <%= link_to "Home", root_path, class: "nav-link#{' active' if controller.controller_name == 'home'}" %> </li> <% if @user_name %> <li class="nav-item" data-turbolinks="false"> <%= link_to "Calendar", "/calendar", class: "nav-link#{' active' if controller.controller_name == 'calendar'}" %> </li> <% end %> </ul> <ul class="navbar-nav justify-content-end"> <li class="nav-item"> <a class="nav-link external-link" href="https://developer.microsoft.com/graph/docs/concepts/overview" target="_blank"> <i class="ms-Icon ms-Icon--NavigateExternalInline mr-1"></i>Docs </a> </li> <% if @user_name %> <li class="nav-item dropdown"> <a class="nav-link avatar-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"> <% if @user_avatar %> <img src=<%= @user_avatar %> class="rounded-circle align-self-center mr-2 profile-photo"> <% else %> <%= image_tag "no-profile-photo.png", class: "rounded-circle align-self-center mr-2 profile-photo" %> <% end %> </a> <div class="dropdown-menu dropdown-menu-right"> <h5 class="dropdown-item-text mb-0"><%= @user_name %></h5> <p class="dropdown-item-text text-muted mb-0"><%= @user_email %></p> <div class="dropdown-divider"></div> <%= link_to "Sign Out", {:controller => :auth, :action => :signout}, :class => "dropdown-item" %> </div> </li> <% else %> <li class="nav-item"> <%= link_to "Sign In", "/auth/microsoft_graph_auth", method: :post, class: "nav-link" %> </li> <% end %> </ul> </div> </div> </nav> <main role="main" class="container"> <% if @errors %> <% @errors.each do |error| %> <div class="alert alert-danger" role="alert"> <p class="mb-3"><%= error[:message] %></p> <%if error[:debug] %> <pre class="alert-pre border bg-light p-2"><code><%= error[:debug] %></code></pre> <% end %> </div> <% end %> <% end %> <%= yield %> </main> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </body> </html>
Este código agrega Bootstrap para un estilo sencillo y Fabric Core para algunos iconos sencillos. También define un diseño global con una barra de navegación.
Abra ./app/assets/stylesheets/application.css y agregue lo siguiente al final del archivo.
body { padding-top: 4.5rem; } .alert-pre { word-wrap: break-word; word-break: break-all; white-space: pre-wrap; } .external-link { padding-top: 6px; } .avatar-link { padding-top: 4px; padding-bottom: 4px; } .profile-photo { width: 32px; }
Genere un controlador de página principal con el siguiente comando.
rails generate controller Home index
Configura la
index
acción en el controlador como la página predeterminada de laHome
aplicación. Abra ./config/routes.rb y reemplace su contenido por el siguienteRails.application.routes.draw do get 'home/index' root 'home#index' # Add future routes here end
Abra ./app/view/home/index.html.erb y reemplace su contenido por lo siguiente.
<div class="jumbotron"> <h1>Ruby Graph Tutorial</h1> <p class="lead">This sample app shows how to use the Microsoft Graph API to access a user's data from Ruby</p> <% if @user_name %> <h4>Welcome <%= @user_name %>!</h4> <p>Use the navigation bar at the top of the page to get started.</p> <% else %> <%= link_to "Click here to sign in", "/auth/microsoft_graph_auth", method: :post, class: "btn btn-primary btn-large" %> <% end %> </div>
Agregue un archivo PNG denominado no-profile-photo.png en el directorio ./app/assets/images.
Guarde todos los cambios y reinicie el servidor. Ahora, la aplicación debe tener un aspecto muy diferente.
Registrar la aplicación en el portal
En este ejercicio, creará un nuevo registro de aplicaciones web de Azure AD mediante el Centro Azure Active Directory administración.
Abra un explorador y vaya al centro de administración de Azure Active Directory. Inicie sesión con una cuenta personal (también conocida como: cuenta Microsoft) o una cuenta profesional o educativa.
Seleccione Azure Active Directory en el panel de navegación izquierdo y, a continuación, seleccione Registros de aplicaciones en Administrar.
Seleccione Nuevo registro. En la página Registrar una aplicación, establezca los valores siguientes.
- Establezca Nombre como
Ruby Graph Tutorial
. - Establezca Tipos de cuenta admitidos en Cuentas en cualquier directorio de organización y cuentas personales de Microsoft.
- En URI de redirección, establezca la primera lista desplegable en
Web
y establezca el valorhttp://localhost:3000/auth/microsoft_graph_auth/callback
.
- Establezca Nombre como
Seleccione Registrar. En la página Tutorial Graph Ruby, copie el valor del identificador de aplicación (cliente) y guárdelo, lo necesitará en el paso siguiente.
Seleccione Certificados y secretos en Administrar. Seleccione el botón Nuevo secreto de cliente. Escriba un valor en Descripción, y seleccione una de las opciones para Expira, y después, Agregar.
Copie el valor del secreto del cliente antes de salir de esta página. Lo necesitará en el siguiente paso.
Importante
El secreto de cliente no se vuelve a mostrar, así que asegúrese de copiarlo en este momento.
Agregar autenticación de Azure AD
En este ejercicio, extenderá la aplicación desde el ejercicio anterior para admitir la autenticación con Azure AD. Esto es necesario para obtener el token de acceso OAuth necesario para llamar a Microsoft Graph. En este paso, integrará la gema omniauth-oauth2 en la aplicación y creará una estrategia de OmniAuth personalizada.
Crea un archivo independiente para contener el identificador y el secreto de la aplicación. Cree un nuevo archivo llamado
oauth_environment_variables.rb
en la carpeta ./config y agregue el siguiente código.ENV['AZURE_APP_ID'] = 'YOUR_APP_ID_HERE' ENV['AZURE_APP_SECRET'] = 'YOUR_APP_SECRET_HERE' ENV['AZURE_SCOPES'] = 'openid profile email offline_access user.read mailboxsettings.read calendars.readwrite'
Reemplace por el identificador de aplicación del Portal de
YOUR_APP_ID_HERE
registro de aplicaciones y reemplace por la contraseña queYOUR_APP_SECRET_HERE
generó.Importante
Si usas el control de código fuente como git, ahora sería un buen momento para excluir el archivo del control de código fuente para evitar la pérdida involuntaria del identificador y la contraseña de la
oauth_environment_variables.rb
aplicación.Abra ./config/environment.rb y agregue el siguiente código antes de la
Rails.application.initialize!
línea.# Load the Rails application. require_relative "application" # Load OAuth settings oauth_environment_variables = File.join(Rails.root, 'config', 'oauth_environment_variables.rb') load(oauth_environment_variables) if File.exist?(oauth_environment_variables) # Initialize the Rails application. Rails.application.initialize!
Configurar OmniAuth
Ya ha instalado la gema, pero para que funcione con los puntos de conexión de OAuth de Azure, debe crear una estrategia omniauth-oauth2
de OAuth2. Se trata de una clase Ruby que define los parámetros para realizar solicitudes de OAuth al proveedor de Azure.
Cree un nuevo archivo llamado
microsoft_graph_auth.rb
en la carpeta ./lib'** y agregue el siguiente código.require 'omniauth-oauth2' module OmniAuth module Strategies # Implements an OmniAuth strategy to get a Microsoft Graph # compatible token from Azure AD class MicrosoftGraphAuth < OmniAuth::Strategies::OAuth2 option :name, :microsoft_graph_auth DEFAULT_SCOPE = 'openid email profile User.Read'.freeze # Configure the Microsoft identity platform endpoints option :client_options, :site => 'https://login.microsoftonline.com', :authorize_url => '/common/oauth2/v2.0/authorize', :token_url => '/common/oauth2/v2.0/token' # Send the scope parameter during authorize option :authorize_options, [:scope] # Unique ID for the user is the id field uid { raw_info['id'] } # Get additional information after token is retrieved extra do { 'raw_info' => raw_info } end def raw_info # Get user profile information from the /me endpoint @raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me?$select=displayName,mail,mailboxSettings,userPrincipalName').parsed end def authorize_params super.tap do |params| params[:scope] = request.params['scope'] if request.params['scope'] params[:scope] ||= DEFAULT_SCOPE end end # Override callback URL # OmniAuth by default passes the entire URL of the callback, including # query parameters. Azure fails validation because that doesn't match the # registered callback. def callback_url options[:redirect_uri] || (full_host + script_name + callback_path) end end end end
Tómese un momento para revisar lo que hace este código.
- Establece el para
client_options
especificar los puntos Plataforma de identidad de Microsoft de conexión. - Especifica que el parámetro
scope
debe enviarse durante la fase de autorización. - Asigna la propiedad
id
del usuario como el identificador único del usuario. - Usa el token de acceso para recuperar el perfil del usuario de Microsoft Graph para rellenar el
raw_info
hash. - Invalida la dirección URL de devolución de llamada para asegurarse de que coincide con la devolución de llamada registrada en el portal de registro de aplicaciones.
- Establece el para
Cree un nuevo archivo llamado
omniauth_graph.rb
en la carpeta ./config/initializers y agregue el siguiente código.require 'microsoft_graph_auth' Rails.application.config.middleware.use OmniAuth::Builder do provider :microsoft_graph_auth, ENV['AZURE_APP_ID'], ENV['AZURE_APP_SECRET'], :scope => ENV['AZURE_SCOPES'] end
Este código se ejecutará cuando se inicie la aplicación. Carga el middleware de OmniAuth con el proveedor, configurado con las variables de entorno establecidas
microsoft_graph_auth
en oauth_environment_variables.rb.
Implementar el inicio de sesión
Ahora que el middleware de OmniAuth está configurado, puedes pasar a agregar el inicio de sesión a la aplicación.
Ejecute el siguiente comando en la CLI para generar un controlador para el inicio de sesión y el cierre de sesión.
rails generate controller Auth
Abra ./app/controllers/auth_controller.rb. Agregue un método de devolución de llamada a la
AuthController
clase. El middleware de OmniAuth llamará a este método una vez completado el flujo de OAuth.def callback # Access the authentication hash for omniauth data = request.env['omniauth.auth'] # Temporary for testing! render json: data.to_json end
Por ahora, todo lo que hace es representar el hash proporcionado por OmniAuth. Lo usarás para comprobar que el inicio de sesión funciona antes de seguir adelante.
Agregue las rutas a ./config/routes.rb.
# Add route for OmniAuth callback match '/auth/:provider/callback', :to => 'auth#callback', :via => [:get, :post]
Inicie el servidor y vaya a
https://localhost:3000
. Haga clic en el botón de inicio de sesión y se le redirigirá ahttps://login.microsoftonline.com
. Inicie sesión con su cuenta de Microsoft y consiente los permisos solicitados. El explorador redirige a la aplicación y muestra el hash generado por OmniAuth.{ "provider": "microsoft_graph_auth", "uid": "eb52b3b2-c4ac-4b4f-bacd-d5f7ece55df0", "info": { "name": null }, "credentials": { "token": "eyJ0eXAi...", "refresh_token": "OAQABAAA...", "expires_at": 1529517383, "expires": true }, "extra": { "raw_info": { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(displayName,mail,mailboxSettings,userPrincipalName)/$entity", "displayName": "Lynne Robbins", "mail": "LynneR@contoso.OnMicrosoft.com", "userPrincipalName": "LynneR@contoso.OnMicrosoft.com", "id": "d294e784-840e-4f9f-bb1e-95c0a75f2f18@2d18179c-4386-4cbd-8891-7fd867c4f62e", "mailboxSettings": { "archiveFolder": "AAMkAGI2...", "timeZone": "Pacific Standard Time", "delegateMeetingMessageDeliveryOptions": "sendToDelegateOnly", "dateFormat": "M/d/yyyy", "timeFormat": "h:mm tt", "automaticRepliesSetting": { "status": "disabled", "externalAudience": "all", "internalReplyMessage": "", "externalReplyMessage": "", "scheduledStartDateTime": { "dateTime": "2020-12-09T17:00:00.0000000", "timeZone": "UTC" }, "scheduledEndDateTime": { "dateTime": "2020-12-10T17:00:00.0000000", "timeZone": "UTC" } }, "language": { "locale": "en-US", "displayName": "English (United States)" }, "workingHours": { "daysOfWeek": [ "monday", "tuesday", "wednesday", "thursday", "friday" ], "startTime": "08:00:00.0000000", "endTime": "17:00:00.0000000", "timeZone": { "name": "Pacific Standard Time" } } } } } }
Almacenar los tokens
Ahora que puede obtener tokens, es el momento de implementar una forma de almacenarlos en la aplicación. Dado que se trata de una aplicación de ejemplo, por motivos de simplicidad, las almacenarás en la sesión. Una aplicación del mundo real usaría una solución de almacenamiento seguro más confiable, como una base de datos.
Abra ./app/controllers/application_controller.rb. Agregue el método siguiente a la clase
ApplicationController
.def save_in_session(auth_hash) # Save the token info session[:graph_token_hash] = auth_hash[:credentials] # Save the user's display name session[:user_name] = auth_hash.dig(:extra, :raw_info, :displayName) # Save the user's email address # Use the mail field first. If that's empty, fall back on # userPrincipalName session[:user_email] = auth_hash.dig(:extra, :raw_info, :mail) || auth_hash.dig(:extra, :raw_info, :userPrincipalName) # Save the user's time zone session[:user_timezone] = auth_hash.dig(:extra, :raw_info, :mailboxSettings, :timeZone) end
El método toma el hash OmniAuth como parámetro y extrae los bits de información relevantes y, a continuación, los almacena en la sesión.
Agregue funciones de accessor a la clase para recuperar el nombre de usuario, la dirección de correo electrónico y el token de acceso
ApplicationController
de la sesión.def user_name session[:user_name] end def user_email session[:user_email] end def user_timezone session[:user_timezone] end def access_token session[:graph_token_hash][:token] end
Agregue el siguiente código a la
ApplicationController
clase que se ejecutará antes de procesar cualquier acción.before_action :set_user def set_user @user_name = user_name @user_email = user_email end
Este método establece las variables que el diseño (en application.html.erb) usa para mostrar la información del usuario en la barra de navegación. Al agregarlo aquí, no tiene que agregar este código en cada acción de controlador. Sin embargo, esto también se ejecutará para acciones en
AuthController
el , que no es óptimo.Agregue el siguiente código a la
AuthController
clase en ./app/controllers/auth_controller.rb para omitir la acción anterior.skip_before_action :set_user
Actualice la
callback
función de la clase para almacenar los tokens en la sesión yAuthController
redirigirlo de nuevo a la página principal. Reemplace la funcióncallback
existente por lo siguiente.def callback # Access the authentication hash for omniauth data = request.env['omniauth.auth'] # Save the data in the session save_in_session data redirect_to root_url end
Implementar el inicio de sesión
Antes de probar esta nueva característica, agregue una forma de cerrar sesión.
Agregue la siguiente acción a la
AuthController
clase.def signout reset_session redirect_to root_url end
Agregue esta acción a ./config/routes.rb.
get 'auth/signout'
Reinicie el servidor y pase por el proceso de inicio de sesión. Debes volver a la página principal, pero la interfaz de usuario debe cambiar para indicar que has iniciado sesión.
Haga clic en el avatar del usuario en la esquina superior derecha para obtener acceso al vínculo Cerrar sesión. Al hacer clic en Cerrar sesión, se restablece la sesión y se devuelve a la página principal.
Actualización de tokens
Si observas de cerca el hash generado por OmniAuth, observarás que hay dos tokens en el hash: token
y refresh_token
. El valor en token
es el token de acceso, que se envía en el encabezado de las llamadas Authorization
API. Este es el token que permite a la aplicación tener acceso al Graph microsoft en nombre del usuario.
Sin embargo, este token es de corta duración. El token expira una hora después de su emisión. Aquí es donde el refresh_token
valor se vuelve útil. El token de actualización permite a la aplicación solicitar un nuevo token de acceso sin requerir que el usuario vuelva a iniciar sesión. Actualice el código de administración de tokens para implementar la actualización de tokens.
Abra ./app/controllers/application_controller.rb y agregue las siguientes
require
instrucciones en la parte superior:require 'microsoft_graph_auth' require 'oauth2'
Agregue el método siguiente a la clase
ApplicationController
.def refresh_tokens(token_hash) oauth_strategy = OmniAuth::Strategies::MicrosoftGraphAuth.new( nil, ENV['AZURE_APP_ID'], ENV['AZURE_APP_SECRET'] ) token = OAuth2::AccessToken.new( oauth_strategy.client, token_hash[:token], :refresh_token => token_hash[:refresh_token] ) # Refresh the tokens new_tokens = token.refresh!.to_hash.slice(:access_token, :refresh_token, :expires_at) # Rename token key new_tokens[:token] = new_tokens.delete :access_token # Store the new hash session[:graph_token_hash] = new_tokens end
Este método usa la gema oauth2 (una dependencia de la gema) para actualizar los
omniauth-oauth2
tokens y actualizar la sesión.Reemplace el método
access_token
actual por el siguiente.def access_token token_hash = session[:graph_token_hash] # Get the expiry time - 5 minutes expiry = Time.at(token_hash[:expires_at] - 300) if Time.now > expiry # Token expired, refresh new_hash = refresh_tokens token_hash new_hash[:token] else token_hash[:token] end end
En lugar de devolver el token de la sesión, primero comprobará si está cerca de expirar. Si es así, se actualizará antes de devolver el token.
Obtener una vista de calendario
En este ejercicio, incorporará el Graph Microsoft en la aplicación. Para esta aplicación, usará la gema httparty para realizar llamadas a Microsoft Graph.
Crear una aplicación Graph auxiliar
Cree una aplicación auxiliar para administrar todas las llamadas a la API. Ejecute el siguiente comando en la CLI para generar la aplicación auxiliar.
rails generate helper Graph
Abra ./app/helpers/graph_helper.rb y reemplace el contenido por lo siguiente.
require 'httparty' # Graph API helper methods module GraphHelper GRAPH_HOST = 'https://graph.microsoft.com'.freeze def make_api_call(method, endpoint, token, headers = nil, params = nil, payload = nil) headers ||= {} headers[:Authorization] = "Bearer #{token}" headers[:Accept] = 'application/json' params ||= {} case method.upcase when 'GET' HTTParty.get "#{GRAPH_HOST}#{endpoint}", :headers => headers, :query => params when 'POST' headers['Content-Type'] = 'application/json' HTTParty.post "#{GRAPH_HOST}#{endpoint}", :headers => headers, :query => params, :body => payload ? payload.to_json : nil else raise "HTTP method #{method.upcase} not implemented" end end end
Tómese un momento para revisar lo que hace este código. Realiza una solicitud GET o POST sencilla a través de la httparty
gema para el extremo solicitado. Envía el token de acceso en el Authorization
encabezado e incluye los parámetros de consulta que se pasan.
Por ejemplo, para usar el make_api_call
método para hacer un GET a , puede https://graph.microsoft.com/v1.0/me?$select=displayName
llamarlo así:
make_api_call 'GET', '/v1.0/me', access_token, {}, { '$select': 'displayName' }
Se basará en esto más adelante a medida que implemente más características de Microsoft Graph en la aplicación.
Obtener eventos del calendario desde Outlook
En la CLI, ejecute el siguiente comando para agregar un nuevo controlador.
rails generate controller Calendar index new
Agregue la nueva ruta a ./config/routes.rb.
get 'calendar', :to => 'calendar#index'
Agregue un nuevo método al Graph auxiliar para obtener una vista de calendario. Abra ./app/helpers/graph_helper.rb y agregue el siguiente método al
GraphHelper
módulo.def get_calendar_view(token, start_datetime, end_datetime, timezone) get_events_url = '/v1.0/me/calendarview' headers = { 'Prefer' => "outlook.timezone=\"#{timezone}\"" } query = { 'startDateTime' => start_datetime.iso8601, 'endDateTime' => end_datetime.iso8601, '$select' => 'subject,organizer,start,end', '$orderby' => 'start/dateTime', '$top' => 50 } response = make_api_call 'GET', get_events_url, token, headers, query raise response.parsed_response.to_s || "Request returned #{response.code}" unless response.code == 200 response.parsed_response['value'] end
Tenga en cuenta lo que está haciendo este código.
- La dirección URL a la que se llamará es
/v1.0/me/calendarview
.- El encabezado hace que las horas de inicio y finalización de los resultados se ajusten a la zona
Prefer: outlook.timezone
horaria del usuario. - Los
startDateTime
parámetros y establecen el inicio y el final de laendDateTime
vista. - El
$select
parámetro limita los campos devueltos para cada evento a solo aquellos que la vista usará realmente. - El
$orderby
parámetro ordena los resultados por hora de inicio. - El
$top
parámetro limita los resultados a 50 eventos.
- El encabezado hace que las horas de inicio y finalización de los resultados se ajusten a la zona
- Para obtener una respuesta correcta, devuelve la matriz de elementos contenidos en la
value
clave.
- La dirección URL a la que se llamará es
Agregue un nuevo método a la aplicación Graph para buscar un identificador de zona horaria de IANA basado en un Windows de zona horaria. Esto es necesario porque Microsoft Graph puede devolver zonas horarias como Windows de zona horaria y la clase Ruby DateTime requiere identificadores de zona horaria IANA.
TIME_ZONE_MAP = { 'Dateline Standard Time' => 'Etc/GMT+12', 'UTC-11' => 'Etc/GMT+11', 'Aleutian Standard Time' => 'America/Adak', 'Hawaiian Standard Time' => 'Pacific/Honolulu', 'Marquesas Standard Time' => 'Pacific/Marquesas', 'Alaskan Standard Time' => 'America/Anchorage', 'UTC-09' => 'Etc/GMT+9', 'Pacific Standard Time (Mexico)' => 'America/Tijuana', 'UTC-08' => 'Etc/GMT+8', 'Pacific Standard Time' => 'America/Los_Angeles', 'US Mountain Standard Time' => 'America/Phoenix', 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', 'Mountain Standard Time' => 'America/Denver', 'Central America Standard Time' => 'America/Guatemala', 'Central Standard Time' => 'America/Chicago', 'Easter Island Standard Time' => 'Pacific/Easter', 'Central Standard Time (Mexico)' => 'America/Mexico_City', 'Canada Central Standard Time' => 'America/Regina', 'SA Pacific Standard Time' => 'America/Bogota', 'Eastern Standard Time (Mexico)' => 'America/Cancun', 'Eastern Standard Time' => 'America/New_York', 'Haiti Standard Time' => 'America/Port-au-Prince', 'Cuba Standard Time' => 'America/Havana', 'US Eastern Standard Time' => 'America/Indianapolis', 'Turks And Caicos Standard Time' => 'America/Grand_Turk', 'Paraguay Standard Time' => 'America/Asuncion', 'Atlantic Standard Time' => 'America/Halifax', 'Venezuela Standard Time' => 'America/Caracas', 'Central Brazilian Standard Time' => 'America/Cuiaba', 'SA Western Standard Time' => 'America/La_Paz', 'Pacific SA Standard Time' => 'America/Santiago', 'Newfoundland Standard Time' => 'America/St_Johns', 'Tocantins Standard Time' => 'America/Araguaina', 'E. South America Standard Time' => 'America/Sao_Paulo', 'SA Eastern Standard Time' => 'America/Cayenne', 'Argentina Standard Time' => 'America/Buenos_Aires', 'Greenland Standard Time' => 'America/Godthab', 'Montevideo Standard Time' => 'America/Montevideo', 'Magallanes Standard Time' => 'America/Punta_Arenas', 'Saint Pierre Standard Time' => 'America/Miquelon', 'Bahia Standard Time' => 'America/Bahia', 'UTC-02' => 'Etc/GMT+2', 'Azores Standard Time' => 'Atlantic/Azores', 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', 'UTC' => 'Etc/GMT', 'GMT Standard Time' => 'Europe/London', 'Greenwich Standard Time' => 'Atlantic/Reykjavik', 'Sao Tome Standard Time' => 'Africa/Sao_Tome', 'Morocco Standard Time' => 'Africa/Casablanca', 'W. Europe Standard Time' => 'Europe/Berlin', 'Central Europe Standard Time' => 'Europe/Budapest', 'Romance Standard Time' => 'Europe/Paris', 'Central European Standard Time' => 'Europe/Warsaw', 'W. Central Africa Standard Time' => 'Africa/Lagos', 'Jordan Standard Time' => 'Asia/Amman', 'GTB Standard Time' => 'Europe/Bucharest', 'Middle East Standard Time' => 'Asia/Beirut', 'Egypt Standard Time' => 'Africa/Cairo', 'E. Europe Standard Time' => 'Europe/Chisinau', 'Syria Standard Time' => 'Asia/Damascus', 'West Bank Standard Time' => 'Asia/Hebron', 'South Africa Standard Time' => 'Africa/Johannesburg', 'FLE Standard Time' => 'Europe/Kiev', 'Israel Standard Time' => 'Asia/Jerusalem', 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', 'Sudan Standard Time' => 'Africa/Khartoum', 'Libya Standard Time' => 'Africa/Tripoli', 'Namibia Standard Time' => 'Africa/Windhoek', 'Arabic Standard Time' => 'Asia/Baghdad', 'Turkey Standard Time' => 'Europe/Istanbul', 'Arab Standard Time' => 'Asia/Riyadh', 'Belarus Standard Time' => 'Europe/Minsk', 'Russian Standard Time' => 'Europe/Moscow', 'E. Africa Standard Time' => 'Africa/Nairobi', 'Iran Standard Time' => 'Asia/Tehran', 'Arabian Standard Time' => 'Asia/Dubai', 'Astrakhan Standard Time' => 'Europe/Astrakhan', 'Azerbaijan Standard Time' => 'Asia/Baku', 'Russia Time Zone 3' => 'Europe/Samara', 'Mauritius Standard Time' => 'Indian/Mauritius', 'Saratov Standard Time' => 'Europe/Saratov', 'Georgian Standard Time' => 'Asia/Tbilisi', 'Volgograd Standard Time' => 'Europe/Volgograd', 'Caucasus Standard Time' => 'Asia/Yerevan', 'Afghanistan Standard Time' => 'Asia/Kabul', 'West Asia Standard Time' => 'Asia/Tashkent', 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', 'Pakistan Standard Time' => 'Asia/Karachi', 'Qyzylorda Standard Time' => 'Asia/Qyzylorda', 'India Standard Time' => 'Asia/Calcutta', 'Sri Lanka Standard Time' => 'Asia/Colombo', 'Nepal Standard Time' => 'Asia/Katmandu', 'Central Asia Standard Time' => 'Asia/Almaty', 'Bangladesh Standard Time' => 'Asia/Dhaka', 'Omsk Standard Time' => 'Asia/Omsk', 'Myanmar Standard Time' => 'Asia/Rangoon', 'SE Asia Standard Time' => 'Asia/Bangkok', 'Altai Standard Time' => 'Asia/Barnaul', 'W. Mongolia Standard Time' => 'Asia/Hovd', 'North Asia Standard Time' => 'Asia/Krasnoyarsk', 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', 'Tomsk Standard Time' => 'Asia/Tomsk', 'China Standard Time' => 'Asia/Shanghai', 'North Asia East Standard Time' => 'Asia/Irkutsk', 'Singapore Standard Time' => 'Asia/Singapore', 'W. Australia Standard Time' => 'Australia/Perth', 'Taipei Standard Time' => 'Asia/Taipei', 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', 'Aus Central W. Standard Time' => 'Australia/Eucla', 'Transbaikal Standard Time' => 'Asia/Chita', 'Tokyo Standard Time' => 'Asia/Tokyo', 'North Korea Standard Time' => 'Asia/Pyongyang', 'Korea Standard Time' => 'Asia/Seoul', 'Yakutsk Standard Time' => 'Asia/Yakutsk', 'Cen. Australia Standard Time' => 'Australia/Adelaide', 'AUS Central Standard Time' => 'Australia/Darwin', 'E. Australia Standard Time' => 'Australia/Brisbane', 'AUS Eastern Standard Time' => 'Australia/Sydney', 'West Pacific Standard Time' => 'Pacific/Port_Moresby', 'Tasmania Standard Time' => 'Australia/Hobart', 'Vladivostok Standard Time' => 'Asia/Vladivostok', 'Lord Howe Standard Time' => 'Australia/Lord_Howe', 'Bougainville Standard Time' => 'Pacific/Bougainville', 'Russia Time Zone 10' => 'Asia/Srednekolymsk', 'Magadan Standard Time' => 'Asia/Magadan', 'Norfolk Standard Time' => 'Pacific/Norfolk', 'Sakhalin Standard Time' => 'Asia/Sakhalin', 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', 'Russia Time Zone 11' => 'Asia/Kamchatka', 'New Zealand Standard Time' => 'Pacific/Auckland', 'UTC+12' => 'Etc/GMT-12', 'Fiji Standard Time' => 'Pacific/Fiji', 'Chatham Islands Standard Time' => 'Pacific/Chatham', 'UTC+13' => 'Etc/GMT-13', 'Tonga Standard Time' => 'Pacific/Tongatapu', 'Samoa Standard Time' => 'Pacific/Apia', 'Line Islands Standard Time' => 'Pacific/Kiritimati' }.freeze def get_iana_from_windows(windows_tz_name) iana = TIME_ZONE_MAP[windows_tz_name] # If no mapping found, assume the supplied # value was already an IANA identifier iana || windows_tz_name end
Abra ./app/controllers/calendar_controller.rb y reemplace todo su contenido por lo siguiente.
# Calendar controller class CalendarController < ApplicationController include GraphHelper def index # Get the IANA identifier of the user's time zone time_zone = get_iana_from_windows(user_timezone) # Calculate the start and end of week in the user's time zone start_datetime = Date.today.beginning_of_week(:sunday).in_time_zone(time_zone).to_time end_datetime = start_datetime.advance(:days => 7) @events = get_calendar_view access_token, start_datetime, end_datetime, user_timezone || [] render json: @events rescue RuntimeError => e @errors = [ { :message => 'Microsoft Graph returned an error getting events.', :debug => e } ] end end
Reinicie el servidor. Inicie sesión y haga clic en el vínculo Calendario de la barra de navegación. Si funciona todo, debería ver un volcado JSON de eventos en el calendario del usuario.
Mostrar los resultados
Ahora puede agregar HTML para mostrar los resultados de una manera más fácil de usar.
Abra ./app/views/calendar/index.html.erb y reemplace su contenido por lo siguiente.
<h1>Calendar</h1> <a href="/calendar/new" class="btn btn-light btn-sm mb-3">New event</a> <table class="table"> <thead> <tr> <th scope="col">Organizer</th> <th scope="col">Subject</th> <th scope="col">Start</th> <th scope="col">End</th> </tr> </thead> <tbody> <% @events.each do |event| %> <tr> <td><%= event['organizer']['emailAddress']['name'] %></td> <td><%= event['subject'] %></td> <td><%= event['start']['dateTime'].to_time(:utc).strftime('%-m/%-d/%Y %l:%M %p') %></td> <td><%= event['end']['dateTime'].to_time(:utc).strftime('%-m/%-d/%Y %l:%M %p') %></td> </tr> <% end %> </tbody> </table>
Esto recorrerá una colección de eventos y agregará una fila de tabla para cada uno.
Quite la
render json: @events
línea de la acción enindex
./app/controllers/calendar_controller.rb.Actualice la página y la aplicación ahora debe representar una tabla de eventos.
Crear un nuevo evento
En esta sección, agregará la capacidad de crear eventos en el calendario del usuario.
Abra ./app/helpers/graph_helper.rb y agregue el siguiente método a la Graph clase.
def create_event(token, timezone, subject, start_datetime, end_datetime, attendees, body) create_event_url = '/v1.0/me/events' # Create an event object # https://docs.microsoft.com/graph/api/resources/event?view=graph-rest-1.0 new_event = { 'subject' => subject, 'start' => { 'dateTime' => start_datetime, 'timeZone' => timezone }, 'end' => { 'dateTime' => end_datetime, 'timeZone' => timezone } } unless attendees.empty? attendee_array = [] # Create an attendee object # https://docs.microsoft.com/graph/api/resources/attendee?view=graph-rest-1.0 attendees.each { |email| attendee_array.push({ 'type' => 'required', 'emailAddress' => { 'address' => email } }) } new_event['attendees'] = attendee_array end unless body.empty? # Create an itemBody object # https://docs.microsoft.com/graph/api/resources/itembody?view=graph-rest-1.0 new_event['body'] = { 'contentType' => 'text', 'content' => body } end response = make_api_call 'POST', create_event_url, token, nil, nil, new_event raise response.parsed_response.to_s || "Request returned #{response.code}" unless response.code == 201 end
Abra ./app/controllers/calendar_controller y agregue la siguiente ruta a la clase CalendarController.
def create # Semicolon-delimited list, split to an array attendees = params[:ev_attendees].split(';') # Create the event create_event access_token, user_timezone, params[:ev_subject], params[:ev_start], params[:ev_end], attendees, params[:ev_body] # Redirect back to the calendar list redirect_to({ :action => 'index' }) rescue RuntimeError => e @errors = [ { :message => 'Microsoft Graph returned an error creating the event.', :debug => e } ] end
Abra ./config/routes.rb y agregue la nueva ruta.
post 'calendar/new', :to => 'calendar#create'
Abra ./app/views/calendar/new.html.erb y reemplace su contenido por lo siguiente.
<h1>New event</h1> <%= form_with url: "/calendar/new" do |form| %> <div class="form-group"> <%= form.label :ev_subject, "Subject" %> <%= form.text_field :ev_subject, class: "form-control", required: true %> </div> <div class="form-group"> <%= form.label :ev_attendees, "Attendees" %> <%= form.text_field :ev_attendees, class: "form-control", placeholder: "Separate multiple email addresses with a semicolon (';')" %> </div> <div class="form-row"> <div class="col"> <div class="form-group"> <%= form.label :ev_start, "Start" %> <%= form.datetime_local_field :ev_start, class: "form-control", required: true %> </div> </div> <div class="col"> <div class="form-group"> <%= form.label :ev_end, "End" %> <%= form.datetime_local_field :ev_end, class: "form-control", required: true %> </div> </div> </div> <div class="form-group mb-3"> <%= form.label :ev_body, "Body" %> <%= form.text_area :ev_body, class: "form-control", rows: "3" %> </div> <%= form.submit "Create", class: "btn btn-primary mr-2" %> <a class="btn btn-secondary" href="/calendar">Cancel</a> <% end %>
Guarda los cambios y actualiza la aplicación. En la página Calendario, seleccione el botón Nuevo evento. Rellene el formulario y seleccione Crear para crear un nuevo evento.
¡Enhorabuena!
Has completado el tutorial de Ruby Microsoft Graph usuario. Ahora que tienes una aplicación de trabajo que llama a Microsoft Graph, puedes experimentar y agregar nuevas características. Visite la información general de Microsoft Graph para ver todos los datos a los que puede acceder con Microsoft Graph.
Comentarios
Proporcione cualquier comentario sobre este tutorial en el repositorio GitHub archivo.
¿Tiene algún problema con esta sección? Si es así, envíenos sus comentarios para que podamos mejorarla.