Rails : i18n : unexpected redirections

In my app, I have different User types like VehicleOwner , GarageOwner, Admin and unidentified/unauthenticated. I wanted to implement localization for English and Finnish. I was successful, but suddenly all my old URLs stopped working; when I tried to access the old URLs I was redirected to login page, and the login page was also not loading – stuck on infinite redirection loop.

Continue reading

Rails | Devise | Internationalization I18n

If you are upgrading your app from uni-lingual to bilingual, you might want to have separate pages for Devise related pages as well. Its simple for simpler implementation of devise like no ‘omniauth’. But if you are including ‘omniauth’ then normal recommended approach will not work.

# Before
devise_for :users, path: '',
  path_names: { sign_up: :signup, sign_in: :login, sign_out: :logout },
  controllers: {
    registrations:      'devise_override/registrations',
    confirmations:      'devise_override/confirmations',
    sessions:           'devise_override/sessions',
    omniauth_callbacks: 'vehicle_owners/omniauth_callbacks'
  }
# After
devise_for :users, only: :omniauth_callbacks, controllers: {omniauth_callbacks: 'vehicle_owners/omniauth_callbacks'}
scope "(:locale)", locale: /en|fi/ do
  devise_for :users, path: '',
             path_names:   { sign_up: :signup, sign_in: :login, sign_out: :logout },
             controllers:  {
                 registrations:      'devise_override/registrations',
                 confirmations:      'devise_override/confirmations',
                 sessions:           'devise_override/sessions'
             }, skip: :omniauth_callbacks

So, it turns out that you cannot scope the Omniauth related URLs, as it makes no sense. You might also see warnings like

Devise does not support scoping OmniAuth callbacks under a dynamic segment
and you have set "/(:locale)/". You can work around by passing
`skip: :omniauth_callbacks` to the `devise_for` call and extract omniauth
options to another `devise_for` call outside the scope. Here is an example:

 devise_for :users, only: :omniauth_callbacks, controllers: {omniauth_callbacks: 'users/omniauth_callbacks'}

 scope '/(:locale)', locale: /ru|en/ do
   devise_for :users, skip: :omniauth_callbacks
 end

Note: Do not forget to restart the application after any major changes.

Devise: Resource specific Logic in Rails Routes : Examples

If you are willing to implement or learn how to route a particular URL request to specific controller on the basis of which role the currently logged-in user is.

# in routes.rb
authenticate :user, -> (user) { user.stylist? } do
  resources :stylist_profiles, only: [:index, :show], path: '/profile'
end

authenticate :user, -> (user) { user.customer? } do
  resources :customer_profiles, only: [:index, :show], path: '/profile'
end

The URL /profile or /profile/1 will route to StylistProfileController if user’s role is Stylist and will route to CustomerProfileController if user’s role is Customer .


condition = -> (u) {
  ['qa', 'development', 'staging'].include?(Rails.env) || (u.admin? && Rails.env.production?)
}

authenticate :user, condition do
  mount Sidekiq::Web => '/sidekiq'
  mount RedisBrowser::Web => '/redis-browser'
end

Some additional examples

# in config/routes.rb
# Home page is root for UnAuthenticated visitors
#  This shall be at the top
unauthenticated :user do
  root 'pages#home', page: 'home'
end

authenticate :user, ->(user) {user.vehicle_owner? } do
  root 'vehicle_owners/quote_requests#index'
end

authenticate :user, ->(user) {user.garage_owner? } do
  root 'garage_owners/quote_requests#index'
end
Image

OmniAuth : Doorkeeper : Devise : Redirect to the application after signup/signin

As you have an oauth provider using `Doorkeeper` and other oauth applications (clients). Its annoying when you have not already logged in to the provider app and you clicked `Sign In With Main App‘ button in the login page. Then the main app throws you at the login page. When you have to go to the login page in your client app and do the login all over again. This is so annoying.

If you want to get auto redirected to the client app after filling the sign-in form in the main app(provider app) then here is the solution

Doorkeeper.configure do
  # Change the ORM that doorkeeper will use (needs plugins)
  orm :active_record

  # This block will be called to check whether the resource owner is authenticated or not.
  resource_owner_authenticator do
    user_id = session["warden.user.user.key"][0][0] rescue nil
    User.find_by_id(user_id) || begin
      session['user_return_to'] = request.url
      redirect_to(new_user_session_url)
    end
  end
  
  ...

`begin .. end` block

In the snipped above; you have seen `begin` which might be new to you been used in such scenario.

`begin` is some how like a `do`..`end` closure however this cannot be stored in a variable.

  • its executed in the instant and binding its defined.
  • it returns the last statement executed inside it
  • returning from it returns from its container function
 > a= begin
?> 12
?> 13
?> end
 > a
 # => 13

Rails : OmniAuth : Doorkeeper : Get access to provider’s api

I assume  you have set up your own OmniAuth provider or have used Providers like Facebook, Twitter, LinkedIn.

If in some condition you need to access the api of the OAuth provider like FB’s Graph API and modifying user data in Linked in or Twitter or even in your own provider, you need to verify your authenticity to the provider.

So make sure you keep the access_token given by the provider safe somewhere in session for future use.

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def the_pact
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    auth_data = request.env["omniauth.auth"]
    @user = Spree::User.from_omniauth(auth_data)
    if @user.persisted?
      set_return_to_path_for_admin if @user.is_admin?
      sign_in_and_redirect @user, :event => :authentication # this will throw if @user is not activated
      
      token = auth_data['credentials']['token']
      session['the_pact_access_token'] = token
      
      set_flash_message(:notice, :success, :kind => "ThePact") if is_navigational_format?
    else
      session["devise.the_pact_data"] = auth_data
      redirect_to new_user_registration_url
    end
  end

Then some where in controller you can use the token to use the provider’s APIs to access some external resource.

To have a token object you can simple do

class ThePact::Client < OAuth2::Client
  def initialize
    super(
        ENV['thepact_app_id'],
        ENV['thepact_secret'],
        site: ENV['oauth_provider_url'],
        parse_json: true
    )
  end
end

class ThePact::Token < OAuth2::AccessToken
  # Return a new OAuth2::AccessToken specific to the app
  # and the user with the given token.
  def initialize(token)
    super(
        ThePact::Client.new,
        token
    )
  end
end


access_token = ThePact::Token.new(session['the_pact_access_token'])
access_token.post('some/url', params: {url: 'params'}, body: {file: upload})

Note:

if you pass `params` hash then the data will be sent via URL and visible to network sniffers. If you are sending form data use `body` instead.

 

Sources:

http://stackoverflow.com/a/5698954/3437900

Rails : Run/Test Stripe Integrated Production database in Localhost

Its not that easy to run Stripe Integrated Rails app’s production database in localhost. Yes you can export-Import the database. You can see this blog post to understand how to load production database to localhost.

Things you need to know

  • You don’t have the logins for users other than you
  • You would be using ‘Test-API Key‘ for stripe in local but the customer objects you stored were created using ‘Live Keys‘ which wont be available using ‘Test Keys
  • There is risk of sending Email/SMS to the production users which is not good.

How to proceed then?

I am assuming we are using Devise for authentication. Since we don’t have passwords to get into those accounts, we need to forcefully update the passwords with our own password.

You remember there is a field called ‘:encrypted_password‘ in Users table. You can batch update the passwords like

new_password_digest = User.new(:password => 'password').encrypted_password
User.all.update_all(encrypted_password: new_password_digest)

This will set all passwords to ‘password‘. Now you will be able to login to all the accounts.

Next Issue: Original Email / SMS

Still there are original email addresses involved. In case of any changes there is still chances of sending Email/SMSes to original customer. So we need to batch update email/phone_numbers as well.

my_email = 'email@shivabhusal.com'
my_phone_number = '9843492222'

non_admin_users = User.joins(:roles).where.not({roles: {name: 'admin'}})
non_admin_users.each do |user|
  email_split = my_email.split('@')

  # downcase is important!
  addition = user.first_name.downcase.gsub(' ', '') rescue ''
  addition << "_#{user.last_name.downcase.gsub(' ', '')}" rescue ''
  addition << "+#{user.roles.first.name}" rescue ''
  email_split.insert(1, "+#{addition}+#{user.id}@")

  user.phone = my_phone_number

  user.email = email_split.join()
  user.save validate: false
end

Next Issue : Stripe Customer / Subscription Objects

aaaaaaaa

How to solve?

I prefer to create a new customer object for every customer in database and also all the subscription objects, card object to be replaced by new card objects, subscription objects and all.

 

 

Rails Deeplinking to inner pages of the app

What is deep linking?

App deep linking is a fancy way to refer to a URL that points to a specific part of an app. It’s analogous to a URL that points to a subfolder of a website. For example if I wanted to send you an article on Rails Developer Community, I would send you a URL that immediately brought you to the article, not send you to the shivabhusal.com main portal to navigate to it yourself from there. Continue reading