Rails Kitchen

It's a place to write on stuff I learned recently.

Password Policy Implementation With Devise and Devise Security Extension

| Comments

One of my recent project was in banking domain. So I had to implement password policies, for that I used devise gem.

Password policies :
1 - Enforce password history - 5 password should be remembered
2 - maximum password age - 30 days
3 - minimum password length - 10 letters
4 - password must meet complexity requirements - Should be a combination of letters, numbers and symbols
5 - Account lockout threshold -5
6 - Account lockout duration - 30 minutes
7 - Email validation - Accept only emails of allowed set of domains

Most of the requirents mentioned above are achivable with simple cofigurations in Devise initializer. But to implement Password expirable, Password archivable and password complexity requirements check I used security extension devise_security_extension.

In this post I assume that we already had devise setup in our project. Now we need to add devise_security_extension in to our project.
1
gem 'devise_security_extension'
After you installed Devise Security Extension you need to run the generator:
1
rails generate devise_security_extension:install
The generator will inject the available configuration options into the existing Devise initializer.When you are done, you are ready to add Devise Security Extension modules on top of Devise modules to any of your Devise models.
1
devise :password_expirable, :password_archivable, :expirable, :lockable
I added this in User model
1
2
3
4
5
class User < ActiveRecord::Base
    devise :database_authenticatable, :registerable, :confirmable,
           :recoverable, :rememberable, :trackable, :validatable,
           :password_expirable, :password_archivable, :expirable, :lockable
end
1 - Enforce password history
Uncomment configuration password_archiving_count and deny_old_passwords in Devise initializer and also add a migration creating old_passwords tables.
1
2
3
4
5
6
7
Devise.setup do |config|
  # How often save old passwords in archive
   config.password_archiving_count = 5

  # Deny old password (true, false, count)
   config.deny_old_passwords = true
end
1
2
3
4
5
6
7
8
create_table :old_passwords do |t|
  t.string :encrypted_password, :null => false
  t.string :password_salt
  t.string :password_archivable_type, :null => false
  t.integer :password_archivable_id, :null => false
  t.datetime :created_at
end
add_index :old_passwords, [:password_archivable_type, :password_archivable_id], :name => :index_password_archivable
2 - maximum password age - 30 days
Uncomment configuration expire_password_after and change to 1.months and also add a migration to store .
1
# config.expire_password_after = 1.months
1
2
3
4
5
create_table :the_resources do |t|
  # other devise fields
  t.datetime :password_changed_at
end
add_index :the_resources, :password_changed_at
Replace our devise model with the_resources.
1
2
3
4
5
create_table :users do |t|
  # other devise fields
  t.datetime :password_changed_at
end
add_index :users, :password_changed_at
3 - minimum password length - 10 letters
1
2
# Range for password length.
config.password_length = 10..128
4 - password must meet complexity requirements - Should be a combination of letters, numbers and symbols
Add password_regex with Regular expression which satisfies our needs.
1
2
# Need 1 char of A-Z, a-z and 0-9 and special charactor
config.password_regex = /(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[\W])/
5 - Account lockout threshold -5
1
2
3
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
config.maximum_attempts = 5
6 - Account lockout duration - 30 minutes
1
2
# Time interval to unlock the account if :time is enabled as unlock_strategy.
config.unlock_in = 30.minutes
7 - Email validation - Accept only emails of allowed set of domains
For this, I added a custom validator which will check the domain name of user entered an email.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
validate :presence_of_domain_in_email

def presence_of_domain_in_email
   email_domain = email.split("@").last.downcase
   allowed_domains = User.allowed_domains
   unless allowed_domains[email_domain]
       errors.add :email, "This email domain is not valid. "
   end
end
  
def self.allowed_domains
  {
      'gmail.com' =>'gmail.com',
      'example.com' => 'example.com',
      'example1.in' =>'example1.in'

  }
end

Apipie-rails - Rails API Documentation Tool

| Comments

Apipie-rails is a DSL and Rails engine for documenting your RESTful API. Instead of the traditional use of #comments, Apipie lets you describe the code, through the code. With Apipie you can specify the methods available to your API, describe every possible parameter. Apipie using Ruby syntax so no need to learn yet another syntax. The documentation is available from within your app (by default under the /apipie path.)

Getting started

Add Apipie to Gemfile
1
gem 'apipie-rails'
after bundle install, initialise Apipie
1
rails g apipie:install
Now confirgure the basic settings in file config/initializers/apipie.rb
1
2
3
4
5
6
7
8
Apipie.configure do |config|
  config.app_name                = "DemoApp"
  config.api_base_url            = "/api/v1"
  config.doc_base_url            = "/apidoc"
  # were is your API defined?
  config.api_controllers_matcher = "#{Rails.root}/app/controllers/api/v1/*.rb"
  config.app_info = "DemoApp REST API "
end
Now you can start documenting your resources and actions

Resource Description:
You can describe a resource on the controller level. The description is introduced by calling resource_description do … end.
The following are some of keywords available (all are optional):
resource_id - How the resource will be referenced in Apipie (paths, see command etc.); by default controller_name.downcase is used.
name - Human readable name of resource. By default class.name.humanize is used.
short - Short description of the resource (it’s shown on both the list of resources, and resource details)
Example is given below :
1
2
3
resource_description do
    short "API for managing User Profile"
end
To see more options refer here

Method Description
This using to describe methods available to your API.
api - Describe how this method is exposed, and provide a short description. The first parameter is HTTP method (one of :GET/:POST/:PUT/:DELETE). The second parameter is the relative URL path which is mapped to this method. The last parameter is the methods short description.
1
api :GET, '/user', "Fetch User Profile"
param - Look at Parameter description section for details.
1
2
3
4
5
6
7
param :user, Hash, :description => "User" do
  param :name, String, :desc => "Name", :required => true
  param :email, String, :desc => "Email", :required => false
  param :gender, String, :desc => "Gender (1: Male, 2: Female)", :required => true
  param :photo, ActionDispatch::Http::UploadedFile, :desc => "User Image", :required => false
  param :address, String, :desc => "Address", :required => false
end
formats - Method level request / response formats.
1
formats ['json']
error - Describe each possible error that can happen while calling this method. HTTP response code and description can be provided.
1
2
error :code => 401, :desc => "Unauthorized"
error :code => 404, :desc => "Not Found", :meta => {:anything => "you can think of"}
description -Full method description, which will be converted into HTML by the chosen markup language processor.
1
2
3
4
description <<-EOS
  == Books
  This API is used to fetch all the books.
EOS
example - Provide an example of the server response; whole communication or response type. It will be formatted as code.
To see more options refer here

Example of a user controller with api documentation is given below
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Api::V1::UsersController < Api::ApiController

  resource_description do
    short "API for managing User Profile"
  end

  api :GET, '/user', "Fetch User Profile"
  description <<-EOS
    == Fetch User Profile
     This API is used to fetch the user profile details.
    === Authentication required
     Authentication token has to be passed as part of the request. It can be passed as parameter(auth_token) or HTTP header(AUTH-TOKEN). 
  EOS
  formats ['json']
  error :code => 404, :desc => "User Profile not yet created"

  def show
    if @user.nil?
      render :status => :not_found, :json => {:message => "User Profile not yet created"}
    else
      render :status => :ok, :json => {:user=>@user}
    end
  end


  api :PUT, '/user', "Update User Profile"
  description <<-EOS
    == Update User Profile
     This API is used to update the user profile details.
    === Authentication required
     Authentication token has to be passed as part of the request. It can be passed as parameter(auth_token) or HTTP header(AUTH-TOKEN). 
  EOS
  error :code => 406, :desc => "User Profile not yet created"
  formats ['json']
  param :user, Hash, :description => "User" do
    param :name, String, :desc => "Name", :required => true
    param :email, String, :desc => "Email", :required => false
    param :gender, String, :desc => "Gender (1: Male, 2: Female)", :required => true
    param :photo, ActionDispatch::Http::UploadedFile, :desc => "User Image", :required => false
    param :address, String, :desc => "Address", :required => false
  end

  def user_profile
    if @user.nil?
      render :status => :not_acceptable, :json => {:message => "User not exists"}
    else
      @user.update_attributes(users_params)
      @user.save!(:validate=>false)
      render :status => :ok, :json => {:user =>@user,:message => "User updated"}
    end
  end

  private

  def users_params
    params.require(:user).permit(:name, :email, :gender,  :photo,:address)
  end
end

we can add example data in doc/apipie_examples.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
users#show:
- verb: :GET
  path: /api/v1/user
  query:
  code: 200
  show_in_doc: 1
  recorded: false
  request_data:
  response_data:
    "user":
        "id": 3
        "email": "eshaiju@gmail.com"
        "name": "Shaiju E"
        "gender": "male"
        "photo_path": "http://localhost:3000/system/users/photos/000/000/003/medium/10297568_10152483241776742_8973517135655971471_s.jpg?1403523880"
        "address": ""

Spree - Add Extra Fields to Product Model With Deface and Decorator

| Comments

Spree is a fully-featured e-commerce solution that can be easily integrated into a Rails application. If you need to turn a Rails app into a store that sells products then Spree is one of the quickest ways to do this. We can customise all the built-in features in Spree and can also and new features and fields to Spree models.

In this post, I am discussing adding an extra field for the uploading company logo into products. For customising view of product form we can use Deface. To add has_attached_file relation to product model we use decorator.
First we need to create migration for adding company logo field to spree_products table.
1
2
3
4
5
class AddCompanyLogoToSpreeProducts < ActiveRecord::Migration
  def change
    add_attachment :spree_products, :company_logo
  end
end
To add has_attached_file relation to product model, add product decorator to file app/model/spree/product_decorator.rb
1
2
3
4
Spree::Product.class_eval do
  has_attached_file :company_logo, :styles => { :medium => "300x300>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
  validates_attachment_content_type :company_logo, :content_type => /\Aimage\/.*\Z/
end
Now we only can add file upload filed to product form using deface.
Deface is a standalone Rails library that enables you to customize Erb templates without needing to directly edit the underlying view file. Deface allows you to use standard CSS3 style selectors to target any element (including Ruby blocks), and perform an action against all the matching elements
Add deface code into app/overrides/company_logo_to_admin_product.rb
1
2
3
4
5
6
7
8
9
10
11
Deface::Override.new(
    :virtual_path   => "spree/admin/products/_form",
    :name           => "company_logo_to_admin_product",
    :insert_bottom  => "[data-hook='admin_product_form_additional_fields']",
    :text           => "
          <p>
                <%= f.label :company_logo %>
                <%= f.file_field :company_logo %>
          </p>
"
)
Thats it , now we can start uploading company logo to each product

Multiple Language Sites in Refinery CMS With I18n

| Comments

Refinery CMS is an Rails-based CMS that supports Rails 3.2 and 4.1. We can make Refinary CMS Multilingual with i18n Translations.
For this add the gem to Gemfile
ruby
1
gem 'refinerycms-i18n'
Run the generator
ruby
1
rails g refinery:i18n
Change the language settings In Refinery’s settings find I18n Translation Frontend Locales (Refinery) and add the ISO country name.
config/initializers/refinery/i18n.rb
ruby
1
2
3
4
5
6
7
8
9
10
11
12
Refinery::I18n.configure do |config|
   config.default_locale = :en

  # config.current_locale = :en

  # config.default_frontend_locale = :en

   config.frontend_locales = [:en,:ar]

   config.locales = {:en=>"English", :ar=>"Arabic"}
   #, :nl=>"Nederlands", :pt=>"Português", :"pt-BR"=>"Português brasileiro", :da=>"Dansk", :nb=>"Norsk Bokmål", :sl=>"Slovenian", :es=>"Español", :it=>"Italiano", :de=>"Deutsch", :lv=>"Latviski", :ru=>"Русский", :sv=>"Svenska", :pl=>"Polski", :"zh-CN"=>"简体中文", :"zh-TW"=>"繁體中文", :el=>"Ελληνικά", :rs=>"Srpski", :cs=>"Česky", :sk=>"Slovenský", :ja=>"日本語", :bg=>"Български", :hu=>"Hungarian", :uk=>"Українська"}
end
That’s it! Visit the Pages tab and you should see flags indicating the page language in the tree.

Edit a page and you’ll see the available languages at the top. Simply select one to add content for that language.

Now we need to add multi language support for our extensions. For this we need to specify this when we create extension with –i18n attribute.
ruby
1
rails g refinery:engine Service title:string description:text icon:image --i18n title description
We can add link to toggle between languages in Frontend web site using following code
1
2
3
4
5
<nav id='locale'>
    <% ::Refinery::I18n.frontend_locales.each do |locale| %>
      <%= link_to_if Globalize.locale.to_s != locale.to_s, locale, {:locale => locale} %>
    <% end %>
</nav>

Custom HTML5 Validation Error Messages With Civem.js

| Comments

HTML5 allows us to implement client-side form validation without any JavaScript coding. However, error messages are hard-coded to the browser. In my recent project I faced situation, I have to use custome error message and show error message in both english and arabic. I achived error message customisation with Civem.js which let you easily change the message to whatever you wish.

Following are steps to add custom error messages to our project.
1 - Grab the latest download
2 - Add the civem.js script to your page
3 - Start using the custom error message attributes on your input, textarea and select elements
4 - To customise required field error message use data-errormessage-value-missing attribute
1
<input type="text" required data-errormessage-value-missing="Something's missing" >
5 - To customise filed type mismatch error message, use data-errormessage-type-mismatch attribute
1
<input type="email" id="one" required data-errormessage-value-missing="Something's missing" data-errormessage-type-mismatch="Invalid!">
6 - For showing pattern missmatch, set custome error message on attribute data-errormessage-pattern-mismatch
there are some other attributes also, that we can use to set specific error messages
1
2
3
4
5
data-errormessage-too-long
data-errormessage-range-underflow
data-errormessage-range-overflow
data-errormessage-step-mismatch
data-errormessage-custom-error

Deploying Ruby on Rails Application to Cloud Application Platform - Openshift

| Comments

OpenShift is a Platform as a Service (PaaS) from RedHat. It’s great for deploying web applications as you can setup/scale/manage apps quickly without any hassle, just like Heroku. It is open source and written in Ruby.
To get started create a free Account. You get 3 small gears (resource container/unit : one small gear is equivalent to 512 MB RAM and 1GB storage) for free. Once you are signed up, install the OpenShift RHC Client Tools by running these commands in a terminal
1
2
gem install rhc
rhc setup
We can deploy rails application by adding OpenShift as a remote repo.
1- Create a new application in oppenshift account, then get the git URL for your new application. This was shown to you when you created your application,through the web console
2- Add your OpenShift repo as a remote repo
1
git remote add openshift <OpenShift repo URL>
3- Configure Database
Since your database address may change, you will need to access it using an environment variable. A random username and password were also generated for you when your application was created. But you don’t need to hardcode it into your application, there are environment variables for that too! Add this configuration to database.yml file
1
2
3
4
5
6
7
8
9
10
production:
  adapter: mysql2
  encoding: utf8
  database: <%=ENV['OPENSHIFT_APP_NAME']%>
  pool: 5
  host: <%=ENV['OPENSHIFT_MYSQL_DB_HOST']%>
  port: <%=ENV['OPENSHIFT_MYSQL_DB_PORT']%>
  username: <%=ENV['OPENSHIFT_MYSQL_DB_USERNAME']%>
  password: <%=ENV['OPENSHIFT_MYSQL_DB_PASSWORD']%>
  socket: <%=ENV['OPENSHIFT_MYSQL_DB_SOCKET']%>
4- Now that your app is configured properly, it’s time to deploy it to OpenShift. To do that, simply run a git push
1
git push openshift master
If everything went well, your app should deploy and be accessible. If not, pay attention to the output from the git push, if anything failed, it will tell you there.
If your application requires some persistent directory for your data then you can use directory (app-root/data/) you can access this directory in your application using environment variable OPENSHIFT_DATA_DIR
5- Managing apps:
To ssh into your server, type:
1
rhc ssh app-name
Navigate to App folder:
1
cd app-root/repo
Migrate Database:
1
bundle exec rake db:migrate RAILS_ENV="production"
If you want to associate your own domain name eg. (www.yourdomain.com) with your Openshift rails application URL then you will need to create URL alias as shown below
1
rhc alias add railsapp www.yourdomain.com
then change cname records in your DNS provider account.

In Page Editing in ‘Active Admin’ - Using Gem ‘Best in Place’

| Comments

Best in place is a j Query based Ajax plug in that can help us to add in place editing to our application that takes profit of Restful server-side controllers to allow users to edit stuff with no need of forms. Usage of this gem in rails application is well documented in github page. You can checkout their live demo here. In this post I concentrating on how we can use best in place gem with is active admin pages.
To add Best in Place to our app we first need to add its gem to our application’s Gemfile and run bundle.
Gemfile
1
gem 'best_in_place', github: 'bernat/best_in_place'
Require best in place in active_admin.js.coffee and initialise it
1
2
3
4
5
#= require best_in_place
#= require best_in_place.purr

$(document).ready ->
  jQuery(".best_in_place").best_in_place()
To make text field editable in active admin show page
1
2
3
row :name do |project|
 best_in_place project, :name, :type => :input,:path =>[:admin,project]
end
To make select field editable
1
2
3
row :status do |project|
  best_in_place project, :status, :type => :select,:path =>[:admin,project],:collection => Project.statuses.map{|status| [status,status]}
end
Textarea can be make editable using following code
1
2
3
row :status_description  do |project|
  best_in_place project, :status_description, :type => :textarea,:path =>[:admin,project]
end
For Editable Datepicker with formated output use display_with
1
2
3
row "Planned Start Date" do |project|
  best_in_place project, :planned_start_date , :type => :date ,:path =>[:admin,project],  :display_with => lambda { |v| v.try(:strftime,'%b %d, %Y') }
end

Nested Forms for Belongs_to Relationship - ActiveAdmin

| Comments

For my current project I had to add nested form for belongs_to relationship. After lots of searching i found a solution.
1
2
3
4
5
class Product<ActiveRecord::Base
 belongs_to :cost, :class_name => 'Currency', foreign_key: 'cost_id'
 accepts_nested_attributes_for :cost
 attr_accessor :cost_id
end
1
2
3
4
5
class Currency < ActiveRecord::Base
 def self.currency_types
   ['SAR','AED','USD','EUR','INR']
 end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ActiveAdmin.registerProductdo
 form do |f|
    f.semantic_errors *f.object.errors.keys
    f.inputs "Details" do
      f.input :name
      f.inputs "cost" do
         f.semantic_fields_for :cost_attributes  do |j|
            j.inputs do
              j.input :currency_type, :as => :select, :collection => Currency.currency_types,:label =>'Cost'
              j.input :value
            end
          end
        end
      end
    end
    f.actions
 end

 controller do
   def permitted_params
    params.permit product: [:name,cost_attributes:[:id,:currency_type,:value]]
   end
 end
end

FullCalendar - Integration With Ruby on Rails

| Comments

FullCalendar is a jQuery plugin that provides a full-sized drag drop calendar like the one below. It uses AJAX to fetch events on-the-fly for each month and is easily configured to use your own feed format (an extension is provided for Google Calendar. It is visually customizable and exposes hooks for user-triggered events (like clicking or dragging an event). It is open source licensed under an MIT license

Step 1 : Download the JQuery fullcalendar plugin by here and add fullcalender.js and fullcalender.css into javascripts and stylesheets folder.Fullcalendar is also available as a gem for Ruby on Rails which fits well with Asset Pipeline.
1
gem 'fullcalendar-rails'
Once the gem is installed, include fullcalendar javascript and css assets to your js and css file
Step 2 : In view page index.html.erb or some other html.erb page add a div with id ‘calendar’
Then add following code to js file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$(document).ready(function() {
   $("#calendar").fullCalendar({
     header: 
     left: "prev,next today",
     center: "title",
     right: "month,agendaWeek,agendaDay"
     defaultView: "month",
     height: 500,
     slotMinutes: 15,
     events: "/dashboard/get_events",
     timeFormat: "h:mm t{ - h:mm t} ",
     dragOpacity: "0.5"
  });
});
Now we need to write a method to fetch Json data which we are going to display on calender
In my example
1
2
3
4
5
6
7
8
9
10
11
class DashboardController < ApplicationController
  respond_to :json
  def get_events
    @task = current_user.tasks
    events = []
    @task.each do |task|
      events << {:id => task.id, :title => "#{task.taskable.try(:name)} : #{task.task}", :start => "#{task.planned_start_date}",:end => "#{task.planned_end_date}" }
    end
    render :text => events.to_json
  end
end
1
2
3
4
5
DemoApp::Application.routes.draw do
  resources :dashboard do
    get :get_events, on: :collection
  end
end
that’s it, now you can see the calendar in your dashboard

ActiveAdmin- Adding a Filter on Has_one Through Association

| Comments

Given Project, Task, and Activity, I’ve managed to set up has_one :through relationship filter with the following:
1
2
3
class Project < ActiveRecord::Base
  has_many :tasks
end
1
2
3
4
class Task < ActiveRecord::Base
  belongs_to :project
  has_many :activities, :dependent => :destroy
end
1
2
3
4
class Activity < ActiveRecord::Base
  has_many :tasks
  has_one  :project, through: :task
end
1
2
3
ActiveAdmin.register User do
  filter :task_project_id, as: :select, collection: Project.all, label: 'Project'
end