Setup Rspec, factory bot and database cleaner for Rails 5.2.6

To configure the best test suite in Rails using the RSpec framework and other supporting libraries, such as Factory Bot and Database Cleaner, we’ll remove the Rails native test folder and related configurations.

To begin, we’ll add the necessary gems to our Gemfile:

group :development, :test do
  # Rspec testing module and needed libs
  gem 'factory_bot_rails', '5.2.0'
  gem 'rspec-rails', '~> 4.0.0'
end

group :test do
  # db cleaner for test suite 
  gem 'database_cleaner-active_record', '~> 2.0.1'
end

Now do

bunde install # this installs all the above gems

If your Rails application already includes the built-in Rails test suite, you’ll need to remove it in order to use the RSpec module instead.

I recommend using RSpec over the Rails native test module, as RSpec provides more robust helpers and mechanisms for testing.

To disable the Rails test suite, navigate to the application.rb file and comment out the following line:

# require 'rails/test_unit/railtie'

inside the class Application add this line:

# Don't generate system test files.
config.generators.system_tests = nil

Remove the native rails test folder:

rm -r test/

We use factories over fixtures. Remove this line from rails_helper.rb

config.fixture_path = "#{::Rails.root}/spec/fixtures"

and modify this line to:

config.use_transactional_fixtures = false # instead of true

This is for preventing rails to generate the native test files when we run rails generators.

Database Cleaner

Now we configure the database cleaner that is used for managing data in our test cycles.

Open rails_helper.rb file and require that module

require 'rspec/rails'
require 'database_cleaner'  # <= add here

Note: Use only if you run integration tests with capybara or dealing with javascript codes in the test suite.

“Capybara spins up an instance of our Rails app that can’t see our test data transaction so even tho we’ve created a user in our tests, signing in will fail because to the Capybara run instance of our app, there are no users.”

I experienced database credentials issues:

➜ rspec
An error occurred while loading ./spec/models/user_spec.rb.
Failure/Error: ActiveRecord::Migration.maintain_test_schema!

Mysql2::Error::ConnectionError:
  Access denied for user 'username'@'localhost' (using password: NO)

Initially, I planned to use Database Cleaner, but later I realized that an error I was experiencing was actually due to a corrupted credentials.yml.enc file. I’m not sure how it happened.

To check if your credentials are still intact, try editing the file and verifying that the necessary information is still present.

EDITOR="code --wait" bin/rails credentials:edit

Now in the Rspec configuration block we do the Database Cleaner configuration.

Add the following file:

spec/support/database_cleaner.rb

Inside, add the following:

# DB cleaner using database cleaner library
RSpec.configure do |config|
  # This says that before the entire test suite runs, clear 
  # the test database out completely
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  # This sets the default database cleaning strategy to 
  # be transactions
  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  # include this if you uses capybara integration tests
  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation
  end

  # These lines hook up database_cleaner around the beginning 
  # and end of each test, telling it to execute whatever 
  # cleanup strategy we selected
  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
end

and be sure to require this file in rails_helper.rb

require 'rspec/rails'
require 'database_cleaner'
require_relative 'support/database_cleaner'  # <= here

Configure Factories

Note: We use factories over fixtures because factories provide better features that make writing test cases an easy task.

Create a folder to generate the factories:

mkdir spec/factories

Rails generators will automatically generate factory files for models inside this folder.

A generator for model automatically creating the following files:

spec/models/model_spec.rb
spec/factories/model.rb

Now lets load Factory bot configuration to rails test suite.

Add the following file:

spec/support/factory_bot.rb

and be sure to require this file in rails_helper.rb

require 'rspec/rails'
require 'database_cleaner'
require_relative 'support/database_cleaner'
require_relative 'support/factory_bot'  # <= here

You can see the following line commented

# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }

You can uncomment the line to make all factories available in your test suite, but I don’t recommend this approach as it can slow down test execution. Instead, it’s better to load each factory as needed.

Here’s the final version of the rails_helper.rb file. Note that we won’t be using Capybara for integration tests, so we’re not including the database_cleaner configuration:

# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails'
require_relative 'support/factory_bot'

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
RSpec.configure do |config|
  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = false

  config.infer_spec_type_from_file_location!

  # Filter lines from Rails gems in backtraces.
  config.filter_rails_from_backtrace!
  # arbitrary gems may also be filtered via:
  # config.filter_gems_from_backtrace("gem name")
end

A spec directory look something like this:

spec/
  controllers/
    user_controller_spec.rb
    product_controller_spec.rb
  factories/
    user.rb
    product.rb
  models/
    user_spec.rb
    product_spec.rb
  mailers/
    mailer_spec.rb
  services/
    service_spec.rb  
  rails_helper.rb
  spec_helper.rb

References:

https://github.com/rspec/rspec-rails
https://relishapp.com/rspec/rspec-rails/docs
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#configure-your-test-suite
https://github.com/DatabaseCleaner/database_cleaner

Model Specs

Lets generate a model spec. A model spec is used to test smaller parts of the system, such as classes or methods.

# RSpec also provides its own spec file generators
➜ rails generate rspec:model user
      create  spec/models/user_spec.rb
      invoke  factory_bot
      create    spec/factories/users.rb

Now run the rpsec command. That’s it. You can see the output from rspec.

➜ rspec
*

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Item add some examples to (or delete) /home/.../spec/models/user_spec.rb
     # Not yet implemented
     # ./spec/models/user_spec.rb:4

Finished in 0.00455 seconds (files took 1.06 seconds to load)
1 example, 0 failures, 1 pending

Lets discuss how to write a perfect model spec in the next lesson.

AWS SNS: How to send SMS to a topic, OTP SMS, promotional or transactional SMS

You can send SMS in 2 ways using AWS SNS.

  1. Subscribe to a topic that is already created in AWS SNS and send sms to all numbers who has the subscription.
  2. Send SMS directly to a mobile number.

You can find the following aws doc as a starting point from web and it describes how to create a topic, subscribe a topic and sending sms to the mobile numbers.

https://docs.aws.amazon.com/code-samples/latest/catalog/code-catalog-ruby-example_code-sns.html

For the demonstration purpose, I use Ruby here.

  1. Subscribe to a topic and send SMS
require 'aws-sdk-sns'  # v2: require 'aws-sdk'

sns = Aws::SNS::Resource.new(region: 'us-west-2')

topic = sns.topic('arn:aws:sns:us-west-2:123456789:MySampleTextTopic')

topic.publish({
  message: 'Hello!'
})

This assumes you already created a topic inside your aws console:

Goto AWS Console

  1. Open AWS SNS
  2. Goto Left side -> Mobile -> text messaging sms
  3. Create a topic

You can also follow the above link to perform an API to create topic, subscribe to a topic and send sms

2. Send SMS directly to a mobile number (eg: Send OTP SMS)

Either you can use aws console to send the sms or SNS API.

AWS console:

  1. Open AWS SNS
  2. Goto Left side -> Mobile -> text messaging sms
  3. We are using transactional text messages
  4. Goto publish text message
  5. Select transactional, add mobile number and publish it

Almost all cases we use an API for sending OTP SMS. For that follow the steps.

SNS API – Steps

gem install aws-sdk-sns 
require 'aws-sdk-sns'
otp = generate_otp
set_sns_client
set_sns_client_attrs
response = publish_sms(otp)
  • Generate an OTP
def generate_otp
    (1000..9999).to_a.sample
end
  • Set SNS client
def set_sns_client
    @sns_client = Aws::SNS::Client.new(
      region: ENV['AWS_SNS_REGION'],
      access_key_id: ENV['AWS_SNS_ACCESS_KEY'],
      secret_access_key: ENV['AWS_SNS_SECRET_KEY']
    )
end

  • Set SNS client attributes
 def set_sns_client_attrs
    @sns_client.set_sms_attributes({
                                     attributes: {
                                       'DefaultSenderID' => SENDER_ID,
                                       'DefaultSMSType' => SMS_TYPE
                                     }
                                   })
end

  • Publish SMS
def publish_sms(otp)
    @sns_client.publish({
                          phone_number: @mobile_no,
                          message: "#{OTM_MSG} #{otp}"
                        })
  end

Here ,

OTM_MSG: ‘Your OTP for login is’

SMS_TYPE : ‘Transactional’ or ‘Promotional’

If you want to send OTP, then it is ‘Transactional’. Else if you want to send some promotional sms of your product then it is ‘Promotional’

SENDER_ID: is the sms header that you already registered with TRAI.

The Steps to add Sender ID in AWS is given below:

For example suppose your sender id is: Zomato

Follow the steps to add our SENDER ID – Zomato to AWS SNS

  1. Sign in to the Amazon SNS console – https://console.aws.amazon.com/sns/home
  2. On the navigation panel, choose Mobile, Text messaging (SMS).
  3. On the Mobile text messaging (SMS) page, in the Text messaging preferences section, choose Edit.
  4. On the Edit text messaging preferences page, in the Details section, do the following:
  5. For Default sender ID , enter the provided sender ID to be used (Zomato) as the default for all messages from your account.
  6. Choose Save changes.

If you don’t want to register sender id, then skip this method: set_sns_client_attrs and publish the sms. It take the sms as ‘Promotional’ and sender id will be 8 character random number. Amazon use this type of sms from International route and it costs you almost $0.02 (Rs. 1.5) per sms. Very high rate. So I recommend to register any sender id that resembles your product or company name, from Jio trueconnect (that is free, link given below) and use it in SNS.

If you don’t know how to register sender id, follow this:

For AWS SNS service, there is 2 way of sending sms.

  1. Local route
  2. International route

For local route the price is Rs. 0.20 per sms
For international route the price will be Rs 1.58 per sms – too high

by default AWS SNS use International route

If you are from India follow the TRAI registration
For considering local route we have to register our use case and message templates with TRAI .

So first register here:
https://www.vilpower.in/
as an enterprise / company with all company details and our purpose

These registration requirements are designed to reduce the number of unsolicited messages that Indian consumers receive, and to protect consumers from potentially harmful messages

You can Register in Jio for free:

The link to register:
https://trueconnect.jio.com/#/home/entity-registration
Select Principal entity and continue

Recently Indian Govt made DLT Registration mandatory for sending sms.

Example:
Take Msg from AD-ZOMATO , here ZOMATO is 6 char sender id that we can give in the service provider and send sms before. But now we have to register this in DLT then only our service provider can use this.

After registering DLT we get an ENTITY ID. This entity id need to be attached in our’s otp service provider for sending otp msgs.

If you are using SNS service for the first time you should increase your SMS quota:

AWS says:

If you're new to SMS messaging with Amazon SNS, request a monthly SMS spending threshold that meets the expected demands of your SMS use case. By default, your monthly spending threshold is $1.00 (USD). You can request to increase your spending threshold in the same support case that includes your request for a sender ID

Because Amazon SNS is a distributed system, it stops sending SMS messages within minutes of the spending quota being exceeded. During this period, if you continue to send SMS messages, you might incur costs that exceed your quota.

https://docs.aws.amazon.com/sns/latest/dg/channels-sms-awssupport-sender-id.html

Requesting increases to your monthly SMS spending quota for Amazon SNS:

https://docs.aws.amazon.com/sns/latest/dg/channels-sms-awssupport-spend-threshold.html

Currently, Amazon SNS supports SMS messaging in the following AWS Regions:

https://docs.aws.amazon.com/sns/latest/dg/sns-supported-regions-countries.html

Reference:

https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Aws/SNS/Client.html

Backup your system databases using Ruby backup gem

Install RVM (Or Rbenv) to manage your Ruby versions

 $ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
 $ curl -sSL https://get.rvm.io | bash 

Restart Terminal and type rvm -v

 $ rvm install 2.5
 $ rvm gemset create backup
 $ rvm gemset use backup
 $ gem install backup
 $ backup generate:model --trigger project_2_backup --archives --storages='s3' --compressor='gzip' --notifiers='mail' 
 Generated configuration file: '/home/ubuntu/Backup/config.rb'.
 Generated model file: '/home/ubuntu/Backup/models/project_2_backup.rb'.
 Usage:
   backup generate:model --trigger=TRIGGER
 Options:
   --trigger=TRIGGER
   [--config-path=CONFIG_PATH]  # Path to your Backup configuration directory
   [--databases=DATABASES]      # (mongodb, mysql, postgresql, redis, riak)
   [--storages=STORAGES]        # (cloudfiles, dropbox, ftp, local, ninefold, rsync, s3, scp, sftp)
   [--syncers=SYNCERS]          # (cloud_files, rsync_local, rsync_pull, rsync_push, s3)
   [--encryptors=ENCRYPTORS]    # (gpg, openssl)
   [--compressors=COMPRESSORS]  # (bzip2, gzip, lzma, pbzip2)
   [--notifiers=NOTIFIERS]      # (campfire, hipchat, mail, presently, prowl, twitter)
   [--archives]
   [--splitter]                 # use `--no-splitter` to disable
                               # Default: true 

Sample Model File

Add the following conf in Backup/models/project_2_backup.rb:

Example for mongodb

 database MongoDB do |db|
     db.name               = "db_name"
     db.username           = "db_username"
     db.password           = "db_pswd"
     db.host               = "localhost"
     db.port               = 27017 
     db.ipv6               = false
     #db.only_collections   = ["only", "these", "collections"]
     db.additional_options = ['--authenticationDatabase=admin']
     db.lock               = false
     db.oplog              = false
   end
   ## 
   # Amazon Simple Storage Service [Storage]
   #
   store_with S3 do |s3|
     # AWS Credentials
     s3.access_key_id     = "YOUR_ACCESS_KEY"
     s3.secret_access_key = "YOUR_SECRET_KEY"
     # Or, to use a IAM Profile:
     # s3.use_iam_profile = true
     s3.region            = "ap-southeast-2" 
     s3.bucket            = "bucket_name"
     s3.path              = "bucket_name_path"
     s3.keep              = 12
     # s3.keep              = Time.now - 2592000 # Remove all backups older than 1 month.
   end 

 # Notification mail infos
 notify_by Mail do |mail|
     mail.on_success           = true
     mail.on_warning           = true
     mail.on_failure           = true
     mail.from                 = "_____@gmail.com"
     mail.to                   = "____@___.com"
     mail.cc                   = "______@_____.com, _____@______.com"
     #mail.bcc                  = "bcc@email.com"
     #mail.reply_to             = "reply_to@email.com" 
     mail.address              = "smtp.gmail.com"
     mail.port                 = 587
     mail.domain               = "domain_name"
     mail.user_name            = "email_username"
     mail.password             = "email_password"
     mail.authentication       = "plain"
     mail.encryption           = :starttls
 end 

Once you’ve setup your configuration, check your work with:

$ backup check

If there are no errors, the check should report:

[2019/03/28 10:02:26][info] Configuration Check Succeeded.

Perform Backup:

$ backup perform --trigger project_2_backup

The Keep Option

keep a specified number of backups in storage. After each backup is performed, it will remove older backup package files based on the keep setting.

keep as a Number

If a number has been specified and once the keep limit has been reached, the oldest backup will be removed.

Note that if keep is set to 5, then the 6th backup will be transferred and stored, before the oldest is removed. So be sure you have space available for keep + 1 backups

keep as Time

When a Time object is set to keep it will keep backups until that time. Everything older than the set time will be removed.

Rails 5.2.0 API Only application with latest Ruby 2.5.1

Check for the new ruby and rails versions
https://www.ruby-lang.org/en/downloads/
https://rubygems.org/gems/rails/versions

Here we are going to install Ruby – 2.5.1 & Rails – 5.2.0 (API only application)

Get rbenv into action
If you are not installed rbenv, you can install it from here:
https://github.com/rbenv/rbenv
After the installation make sure that, your $PATH has included rbenv/shims path. Else rbenv will not work.

1. $ rbenv install --list # Gets the list of ruby versions available

$ rbenv install 2.5.1

ruby-build: definition not found: 2.5.1

The following versions contain `2.5.1' in the name:
  rbx-2.5.1

See all available versions with `rbenv install --list'.

If the version you need is missing, try upgrading ruby-build:

  brew update && brew upgrade ruby-build

Oops..!

rbenv cannot find the version: 2.5.1

Upgrade ruby-build

Mac OSX:

$ brew upgrade ruby-build --HEAD

Now install ruby 2.5.1

$ rbenv install 2.5.1

Create a new gemset:

Rbenv gemset is a separate script and not coming with rbenv. If you are not installed this, you can install it from here:
https://github.com/jf/rbenv-gemset

$ rbenv gemset create 2.5.1 demo-app
That set up a directory for you in ~/.rbenv/versions/2.5.1/gemsets/demo-app

Set the ruby version to the newest

$ rbenv local 2.5.1

$ rbenv version
=> 2.5.1

    Activate New Gemset


For activating a gemset we need to create a .rbenv-gemsets file in the current directory.

$ touch .rbenv-gemsets
$ echo demo-app > .rbenv-gemsets

Check active gemset:

$ rbenv gemset active

Install Rails 5.2.0 API only Application

$ gem install rails -v '5.2.0'

$ rails -v
Rails 5.2.0

Later we can delete this .rbenv-gemsets file and add a new file named ‘.ruby-gemset’ in the rails project directory. I cannot find any other option for doing this. If anyone know more about this, please give a comment. I appreciate that.

Create a New Rails app

$ rails new demo-app  --api -T # API only skip the testing framework altogether

For Full Rails:

$ rails new demo-app -T -d postgresql # skip the testing framework altogether, uses Postgres Database

-T to exclude Minitest – the default testing framework if you are planning to use RSpec to test your API.

Rspec test framework:
https://github.com/rspec/rspec-rails

You can use the following with Rspec.
Shoulda Matchers:
Collection of testing matchers extracted from Shoulda (http://matchers.shoulda.io)
https://github.com/thoughtbot/shoulda-matchers

Database Cleaner:
Strategies for cleaning databases in Ruby. Can be used to ensure a clean state for testing
https://github.com/DatabaseCleaner/database_cleaner

Faker:
A library for generating fake data such as names, addresses, and phone numbers.
https://github.com/stympy/faker

use option: –no-rdoc –no-ri # skips the documentation

Remove Rbenv Gemset and add Ruby gems file

$ rm .rbenv-gemsets

$ cd demo-app
$ touch .ruby-gemset
$ echo demo-app > .ruby-gemset
$ rails s
=> Booting Puma
=> Rails 5.2.0 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.11.4 (ruby 2.5.1-p57), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

Goto http://localhost:3000/

rails-5.2.api-application

Done! Lets go…

#Rails 4.2: How to create a full URL with given host and port

Basically if you need to generate url based on the current url during a request OR you can create your own Urls by using Rails ‘ActionDispatch::Integration::Session‘ class.

Rails creates an object ‘app’. It is and action dispatch session object.

You can make use of that object for creating your own URLs like:

> app.root_url(:port => 20)  => "http://www.example.com:20/"

> app.root_url(:port => 20, :host => 'www.bing.com')
 => "http://www.bing.com:20/"

During a request you can use like this:

 > request.url(:port => 20)

How to send Basic Authentication (BA) credentials in ruby on rails

HTTP Basic authentication is a simple authentication scheme in which user authentication is done by a username and password eliminating the needs of cookies, sessions and login pages. It is base64 encoded.

Rails provides a method for this type of authentication: authenticate_with_http_basic

This method can be used as follows:

authenticate_with_http_basic do |username, password|
  ——
end

Inside the block you can access the username and password.

But how to send a request with basic auth in rails ?

This is really tricky and I didn’t find any documentation for this. Here I am describing how to send a request with basic auth.

1. Use Faraday library (Faraday is an HTTP client lib)
https://github.com/lostisland/faraday

Create a connection:

connection = Faraday.new(:url => HOST) do |faraday|
      faraday.request  :url_encoded             # form-encode POST params
      faraday.response :logger                  # log requests to STDOUT
      faraday.adapter  Faraday.default_adapter  # make requests with Net::HTTP
      faraday.basic_auth(USERNAME, PASSWORD)
end

parameters = params[‘user’].permit!.to_h

response = connection.get do |req|
      req.url(params[:url])
      req.headers['Content-Type'] = 'application/json'
      req.params.merge!(parameters)
end

response = connection.post do |req|
        req.url(params[:url])
        req.headers['Content-Type'] = 'application/json'
        req.body = parameters
end

render json: response.body

Here,

HOST = 'http://lvh.me:3002'
USERNAME = 'EdcddzrbmET55016'
PASSWORD = 'UXBJnS309S49st3rHqmH5934'
params['url'] = '/users/profile'

We can use Faraday’s (‘faraday.basic_auth’) basic auth method to reach out our solution.

If you are getting any error like follows in post request

NoMethodError (undefined method `bytesize' for {}:ActiveSupport::HashWithIndifferentAccess):

Don’t forget to convert the ruby hash ‘parameters’ to json by calling ‘to_json’ upon it.

        req.body = parameters.to_json 

In Other way you can use something like this to generate the basic auth header

request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(USERNAME, PASSWORD)

How to use Ruby Rest Client:

require 'rest_client'

headers = {
  :authorization => 'Basic FytxhZGKpbjpvcGVuIHNlc2FtHUHU'
}

response = RestClient.get 'https://yourdomain.com/api/users.json?activityId=02ddf868-6484-440f-8c39-c7d4fb4e7b33', headers
puts response

Rbenv: Start with new ruby and rails versions

Check for the new ruby and rails versions
https://www.ruby-lang.org/en/downloads/
https://rubygems.org/gems/rails/versions

Here we are going to install Ruby – 2.4.1 & Rails – 5.1.3

Get rbenv into action
If you are not installed rbenv, you can install it from here:
https://github.com/rbenv/rbenv
After the installation make sure that, your $PATH has included rbenv/shims path. Else rbenv will not work.

1. $ rbenv install --list # Gets the list of ruby versions available

$ rbenv install 2.4.1

ruby-build: definition not found: 2.4.1

The following versions contain `2.4.1' in the name:
  rbx-2.4.1

See all available versions with `rbenv install --list'.

If the version you need is missing, try upgrading ruby-build:

  brew update && brew upgrade ruby-build

Oops..!

rbenv cannot find the version: 2.4.1

Upgrade ruby-build

Mac OSX:

$ brew upgrade ruby-build --HEAD

Now install ruby 2.4.1

$ rbenv install 2.4.1

Create a new gemset:

Rbenv gemset is a separate script and not coming with rbenv. If you are not installed this, you can install it from here:
https://github.com/jf/rbenv-gemset

$ rbenv gemset create 2.4.1 demo-app
That set up a directory for you in ~/.rbenv/versions/2.4.1/gemsets/demo-app

Set the ruby version to the newest

$ rbenv local 2.4.1

$ rbenv version
=> 2.4.1

    Activate New Gemset


For activating a gemset we need to create a .rbenv-gemsets file in the current directory.

$ touch .rbenv-gemsets
$ echo demo-app > .rbenv-gemsets

Check active gemset:

$ rbenv gemset active

Install Rails 5.1.3

$ gem install rails -v '5.1.3'
$ gem install --no-rdoc --no-ri rails -v '5.1.3' # skips the documentation

Later we can delete this .rbenv-gemsets file and add a new file named ‘.ruby-gemset’ in the rails project directory. I cannot find any other option for doing this. If anyone know more about this, please give a comment. I appreciate that.

Create a New Rails app

$ rails new demo-app

$ rm .rbenv-gemsets

$ cd demo-app
$ touch .ruby-gemset
$ echo demo-app > .ruby-gemset
$ touch .ruby-version
$ echo 2.4.1 > .ruby-version
$ rails s
=> Booting Puma
=> Rails 5.1.3 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.9.1 (ruby 2.4.1-p111), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

Goto http://localhost:3000/

rails-5-new.png

Done! Lets go…

#Rails 4.2 #Ruby2.2 How to find association class and other info from an object and its association name

When I was working on a Rails project, I encountered a situation where I needed to find the association class of an association object. I have the object and its association name as inputs. How can I find the association class?

Suppose we have Student class that belongs to a school

class School
  has_many students
end

class Student
  belongs_to :school
end

and suppose so many other relations like this in our project.

So we have

s = Student.last
:school symbol 

I can use

s.school.class and s.school.class.name

But what if the school is blank? The result is ‘NilClass’ From the above code.

Basically for has_many associations now we get the class name as

"ActiveRecord::Associations::CollectionProxy"

because recently in new rails version a change of the Array of objects as associations to its own ‘CollectionProxy’ collections.

So we can use ‘ActiveRecord::Reflection::ClassMethods’ for finding all the association info.

Note that this Rails module is so useful to find all the association related information.

In the above situation we can use ‘reflect_on_association’ method for finding association reflection info. And it returns ‘ActiveRecord::Reflection’ Object.

In the most recent version of Rails, there has been a change where Array of objects as associations have been replaced with their own ‘CollectionProxy’ collections. As a result, we can now use ‘ActiveRecord::Reflection::ClassMethods’ to find all the association information we need. This module is extremely useful in finding all association-related information.

To find association reflection information in the situation described above, we can use the ‘reflect_on_association’ method. This method will return an ‘ActiveRecord::Reflection’ object.

http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html#method-i-reflect_on_association

Check the following code:

> s.class.reflect_on_association(:school)
=> ##}, @scope_lock=#
, @class_name="Topic", @foreign_key="school_id">

Namespaced classes in Ruby

We can write namspaced classes in ruby in two ways.

Normal way we can wrap the class inside a module. Lets say the module name as ‘MyModule’.
And the constants we define inside this module are accessed as follows:


module MyModule
    CONST1 = 1
    class Myclass
       CONST2 = 2
       def name
          "This is my name"
       end

       def const_1
         CONST1
       end

       def const_2
          CONST2
       end
    end
end

p MyModule::Myclass.new.name
p MyModule::Myclass.new.const_1
p MyModule::Myclass.new.const_2

The other way of doing this is the short way of writing the class name with module name and two columns.
As you can see, the const_1 is accessed as prefixing the module name with two columns.

module MyModule
    CONST1 = 1
end

class MyModule::Myclass
    CONST2 = 2
    
    def name
       "This is my name"
    end
    
    def const_1
        MyModule::CONST1        
    end
    
    def const_2
        CONST2
    end
end

p MyModule::Myclass.new.name
p MyModule::Myclass.new.const_1
p MyModule::Myclass.new.const_2

There is an another way of doing this, that may looks strange to most of the people. Nested classes.

class Myclass
  def name
     "This is my name"
  end

  def my_class_2_name
     Myclass2.new.name
  end

  class Myclass2
    def name
       Myclass.new.name
    end
  end
end

> p Myclass.new.name
> "This is my name"
> p Myclass.new.my_class_2_name
> "This is my name"

The two printing works. So what is the use of these nested classes? Hmmm. It is just namespacing the second class and it tells, somehow it relates to first class even though there is no relation between these two classes.

> p Myclass2.new.name
> uninitialized constant Myclass2

We cannot access Myclass2 without specifying the namespace ( Myclass )

> p Myclass::Myclass2.new.name
> "This is my name"

Set up capistrano deployment for Ruby On Rails

STEP 1:
Install capistrano gem

group :test, :development do
  gem 'capistrano'
end

Install capistrano with rvm

gem 'rvm-capistrano'

STEP 2:
Prepare your Project for Capistrano
Capify your project. The following command initialise your project with Capistrano.

$ capify .

STEP 3:
Do proper modificatons in Capistrano Recipe (config/deploy.rb)
http://guides.beanstalkapp.com/deployments/deploy-with-capistrano.html

Lets do the deployment for staging environment.
Create a ruby file under config/deploy/ folder named staging.rb
Copy the following content

set :domain, "mydomain.in"
role :app, domain
role :web, domain
role :db, domain, :primary => true
role :resque_worker, domain   # if you are using workers in your project, set role for them if needed
role :resque_scheduler, domain # if you are using workers in your project

set :deploy_to, "/home/my_deploy_path/"  # the deployment directory
set :environment, "staging"
set :rails_env, "staging"
set :branch, "staging"
set :previous_environment, "develop"

STEP 4:
Setup capistrano in deployment server

$ cap staging deploy:setup

This will Create folder structure that capistrano uses in the process.

Make sure that everything is set up correctly on the server by the command

$ cap staging deploy:check

Now you can see a message like:
“You appear to have all necessary dependencies installed”

Create shared/config folder in your deploy_to path
and copy database.yml and other config files as you written in the symlink_shared task in cap recipie (if any)

STEP 5:
Deploy your project:

cap staging deploy