Wednesday, May 6, 2020

Errno::ENOSPC: No space left on device

Recently one of our EC2 instance was down due to insufficient disk space. To make it up immediately we had increased its disk space dynamically using EC2 console.

After debugging we found that there was an unexpected spike in rails logs ( some custom logs also ) and logrotate failed to rotate them due to insufficient memory.


After increasing memory, we still, getting ‘Errno::ENOSPC: No space left on device’ in some Sidekiq jobs. Every though more than 50%  disk space was available.





After googling, got that might be running out of inodes, but it seems that also not an issue.





But in the above output found a strength file system for /tmp called 'overflow'. So googled out on that term and got the exact reason for an issue.


Here is that reason,


When the system boots and the hard drive is full, nothing can write to /tmp. So during init, a tmpfs is created and mounted. This way your system can safely boot because it can write to /tmp.


So started looking on, how to reset back tmp, found many solutions but those seem to be risky, but out of that found nice solution provided by Jarrod’s blog.


He just asked to unmount an overflow partition so that /tmp will go back to the normal pointing.


sudo umount overflow

But after running this command was getting an error,

umount: /tmp: device is busy.

              (In some cases useful info about processes that use
               the device is found by lsof(8) or fuser(1))

Again his blog helped. Found comment in his blog for exactly the above issue.


sudo umount -l /tmp

And finally /tmp got mounted back to normal.

Friday, May 6, 2016

How to test model validations with validation type

While reading a code or while doing code review, I have see many times people write a test cases for model validation like this,
Before
# app/models/post.rb
class Post < ActiveRecord::Base
  validates :title, presence: true
end
# test/models/post_test.rb
test "should have the necessary required validators" do
  post = Post.new

  assert_not post.valid?
  assert post.errors.has_key(:title)
  # or some times
  assert_equal ["can't be blank"], post.errors.messages[:title]
end
And if same attribute has more than one validation then,
# app/models/post.rb
class Post < ActiveRecord::Base
  validates :title, presence: true, length: { in: 10..60 }
end
Most of the people do,
test "should have the necessary required validators" do
  post = Post.new

  assert_not post.valid?
  assert_equal ["can't be blank", "is too short (minimum is 10 characters)"], post.errors.messages[:title]
end
I think instead we can use symbol for it. Rails internally uses I18n locale support for error messages. For more details have a quick look on reference links provided below.

Means we can take a benifits from it. So improved test case code is,
After
test "should have the necessary required validators" do
  post = Post.new
  assert_not post.valid?
  
  assert post.errors.added? :title, :blank
  assert post.errors.added? :title, :too_short, { count: 20 }
end
While for custom validate method, you can use same approch by defining new key. Key can be any valid symbol.
Before
class Post < ActiveRecord::Base
  has_many :tags

  validates :title, presence: true
  validate :validate_tags_count
  
  private
    def validate_tags_count
      errors.add(:tags, "required alteast 2 tags") if tags.reject(&:marked_for_destruction?).count < 2
    end
end
After
class Post < ActiveRecord::Base
  has_many :tags

  validates :title, presence: true
  validate :validate_tags_count
  
  private
    def validate_tags_count
      errors.add(:tags, :required, count: 2) if tags.reject(&:marked_for_destruction?).count < 2
    end
end
test "should have the necessary required validators" do
  post = Post.new

  assert_not post.valid?
  assert post.errors.added? :tags, :required, { count: 2 }
end
#config/locales/en.yml
# Globally for all models
en:
  errors:
    messages: 
      required: "minimum %{count} tags required" 

or
 
#config/locales/en.yml
# only to post models
en:
  activerecord:
    errors:
      models:
        post:
          attributes:
            tags:
              required: "minimum %{count} tags required"
Same concept will work on rails 5 as well. Tested with rails 5.0.0.beta4.

References :
http://api.rubyonrails.org/classes/ActiveModel/Errors.html
activemodel/lib/active_model/validations/presence.rb
activemodel/lib/active_model/locale/en.yml

Monday, January 5, 2015

Ruby 2.2.0 installation using rbenv failing

On my ubuntu 14.04 development machine, ruby 2.2.0 installation using rbenv was failing with following error
➜  ~  rbenv install 2.2.0
Downloading ruby-2.2.0.tar.gz...
-> http://dqw8nmjcqpjn7.cloudfront.net/7671e394abfb5d262fbcd3b27a71bf78737c7e9347fa21c39e58b0bb9c4840fc
Installing ruby-2.2.0...

BUILD FAILED (Ubuntu 14.04 using ruby-build 20141225-7-g4aeeac2)

Inspect or clean up the working tree at /tmp/ruby-build.20150105132503.8460
Results logged to /tmp/ruby-build.20150105132503.8460.log

Last 10 log lines:
make[1]: *** [ext/fiddle/all] Error 2
make[1]: *** Waiting for unfinished jobs....
installing default nkf libraries
installing default date_core libraries
linking shared-object date_core.so
make[2]: Leaving directory `/tmp/ruby-build.20150105132503.8460/ruby-2.2.0/ext/date'
linking shared-object nkf.so
make[2]: Leaving directory `/tmp/ruby-build.20150105132503.8460/ruby-2.2.0/ext/nkf'
make[1]: Leaving directory `/tmp/ruby-build.20150105132503.8460/ruby-2.2.0'
make: *** [build-ext] Error 2
I have resolved this issue by installing following dependencies
sudo apt-get install autoconf bison libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm3 libgdbm-dev
References:
https://github.com/sstephenson/ruby-build/wiki

Sunday, May 25, 2014

How to: Override devise default routes for sign in and sign out

In one of my application, I need to override default routes provided by Devise for better SEO.

SEO expert asked me to change default url in such way,
"/users/sign_in" => "/sign-in"
"/users/sign_up" => "/sign-up"

And I know, it is possible in devise. Devise is awesome authentication gem which provides lots of customization as per our app needs. In fact, every gem publisher should study the devise code for better understanding on how to provide api's for customization.

So here is the full customization of default routes,

redirect section : 
        On line 5 and 6, I wrote a redirection rule for SEO purpose and for demonstration over here. But best place is to place redirection rules in your web server, that is in nginx or apache.
        This only applies to you, if your app is already live and search engine has already indexed your sign in and sign out routes.

skip section :
        On line 10, I have skipped generation of default routes for session(sign_in) and registration(sign_up).

devise_scope block :
        In devise_scope block, we need to provide our own urls for sign_in and sign_out. So we need to provide url format with controller and action.
        Also, for each urls, we need to provide path helper using ":as", so that we can still use old path name, without code change.

Tuesday, May 6, 2014

Upgrading from Rails 4.0 to rails 4.1 - My experience

TL;DR: I have recently upgraded one of my rails app from rails version 4.0 to 4.1 and collected steps over here.

Before starting upgrade I went through following link and it is definitely worth to read those links,
1. Rails 4.1 release notes
2. Rails 4.1 upgrade steps

These link almost covered every thing which we need to for rails 4.1 upgrade. Thanks to rails team.

While upgrading you have to note that, your app might be using some gems which is not yet rails-4.1 compatible or might be it's dependent gem.
In my case it was, "sidekiq-failures" which was not compatible with sidekiq-3.0.0 and sidekiq-3.0.0 was rails-4.1 compatible. So I have disabled "sidekiq-failures" for now. ( Author of gem is working on it and might be he has fixed it be the now ).

Here are my steps for rails upgrade.
1. bundle update :
        Bundle update is our routine process, but still I have started with it. So that my stack is ready with all latest gems. You can use "bundle outdated" to check which of your gems are outdated and how much. If major update is happening with any gem then you must have to look into its release notes.

2. Rails 4.1 update and bundle update again :
        Then I have updated rails version first and then bundle update again. Along with this update I have referred rails upgrade steps guide and followed steps which was applicable to my apps.

Out of that important once are,
1. Changes to config/secrets.yml
2. Changes to test helper
3. Addition of spring gem

These are important changes and you have to do it before proceeding.

3. Spec run and code fixes : 
        Running test cases over here is very much important to quickly identify issue with code.
Here are some of failing cases,
a. has_many with through relation :
one of my relation was failing with wrong query
relation was post has_many badges
has_many :badges, -> { order "badgings.id DESC" }, through: :badgings
and failing query is,
"SELECT "badges".* FROM "badges" ORDER BY badgings.id DESC"
So here I was ordering badges using through relation, which is indeed wrong, but I was surprised why it was working previously and not after upgrade. I tried to find cause but not able to find it.
Please comment here, if you know the cause.

For fixing I have changed above line to,
has_many :badges, -> { order "badges.id DESC" }, through: :badgings
b. Complex count quires :
The Major changes, I did with count queries in all pagination. I have changed
@tags.all.count
to
@tags.to_a.count
Please see https://github.com/rails/rails/pull/10710

4. Removed mail_view gem ( If you are using ): 
        Now mail_view gem is part of rails 4.1. As, author of mail_view gem has integrated his gem into rails so there is very less change need to do.
- remove mail_view routes mounting from routes 
- and search and replace MailView to ActionMailer::Preview

Apart from these steps, I have followed following steps which are only applicable if your are using sidekiq-3 or acts-as-taggable-on-3.1
1. For sidekiq :
If you are using Sidekiq for back-grounding and Capistrano for deployment, then your Capistrano script will fail if you have upgraded to Sidekiq-3. Since in Sidekiq-3, Capistrano integrated support has been removed.
To make Capistrano script to work, we need to use "capistrano-sidekiq" gem.
- Just add "capistrano-sidekiq" gem to Gemfile
- And replace "sidekiq/capistrano" to "capistrano/sidekiq" in Capfile.

2. For acts-as-taggable-on :
If you have upgraded acts-as-taggable-on-3.1.1 then you need to run the following generator,
rake acts_as_taggable_on_engine:install:migrations
Since acts-as-taggable-on has added some new columns in tagging system.

And done. Now my app is working on rails 4.1 very smoothly. 


Monday, October 14, 2013

Testing Responsive web design with Rspec

Recently I am working on an application where we have used zurb foundation for responsive web design. We have separated all devices screen broadly into three categories as follows,

1. small : Screen width upto 590.
2. medium : Screen width upto 1025.
3. large : Screen width uptp 1280.

Very soon we will add new category,
4. xlarge : above 1280+ ;)

Now all things are fine, but until you didn't write acceptance test, you can't make sure that your design is working fine for all screen.

Before taking responsive web design into consideration, we have already written feature specs using rspec + capybara (selenium).

So here we want handy configuration which will not affect existing feature spec and should treat existing specs meant to be written for large screen.

So here is, how I have added configuration for my rspec suite to target testing responsive design.


With above configuration, if you write any feature spec without :device_size then it will run that spec against 'large' screen. And if you want to write a spec for 'small' and 'medium' devices you can write by using metadata 'device_size => :small' or 'device_size => :medium'
e.g.
  feature "XYZ" do
    scenario "abc", :js => true do
      # spec with default screen size i.e large
    end 

    scenario "abc", :js => true, :device_size => :small do
      # spec with small screen size
    end 
  end
With the help of 'config.include ScreenSize, :type => :feature' you can directly change screen size into example.
e.g.
  feature "XYZ" do
    scenario "abc", :js => true do
      set_screen_size(:medium)
      # spec with default screen size i.e large
    end 
  end
If you want to some special configuration based on device size you can do that using,
  config.before(:each, :device_size => :small) do
    # special configuration .........
  end
TODO :
I want to split a test suite into feature/small/*.rb for small devices, feature/large/*.rb for large devices and so on.. and apply specific metadata configuration based on type of suite.

References :
http://www.blaulabs.de/2011/11/22/acceptance-testing-with-responsive-layouts

Sunday, September 22, 2013

Capybara wait for ajax call to finish

TL;DR: Before capybara 2.0.0, wait_until method was available which can be used for wait for ajax call to finish. In capybara 2.0.0, wait_until method is removed as it is not needed and was creating confusion. Capybara automatically waits for elements to appear or disappear on the page. If you do something like find("#foo"), this will block until an element with id “foo” appears on the page, as will has_content?("bar"), click_link("baz") and most other things you can do with Capybara.

Since today morning I was facing very strange issue with capybara feature spec. One of my spec was failing with following error.
spec:
    scenario "can create comment" do
      user =  FactoryGirl.create(:user, :email => "p@abc.com")
      post = FactoryGirl.create(:published_post)

      login(user)

      visit post_path(post)

      page.find('#comment_body').set("This is my comment")
      keypress_script = "var e = $.Event('keypress', { keyCode: 13 }); $('#comment_body').trigger(e);"
      page.execute_script(keypress_script)
   
      page.should have_text("This is my comment")
      page.should have_link('Reply')
      page.should have_link('Edit')
      page.should have_link('Destroy')
    end

       expected to find text "This is my comment" in "BROWSE CATEGORIES FORMATS MY PREGNANCY VOICES SERVICES POST TAGS MyText About Author1 — A brief write up about the author will come here. This will not exceed more than 250 characters. Give us your opinion. Discuss it with people like you. Javier Huel - This is my comment 0 likes | 0 replies LIKE | Reply | Edit | Destroy SIMILAR POSTS about us | our team | our vision | give feedback | privacy policy | terms of use | best viewed in | careers | site map © 1998–2013 ABC, Inc. All rights reserved."
     # ./spec/features/commets_spec.rb:42:in `block (3 levels) in ' 

Now in above error, I am clearly able to see that the text is present in page, but still it is not able to match it.

To quickly understand what is happening, I have used pry. On pry prompt, just verified expectation again and this time it is evaluated true.
[1] pry(#)> page.should have_text("This is my comment")
=> true 

Means, here capybara was not waiting for ajax call to finish.

So I have googled for "capybara + wait for ajax call to finish" and found solution to use "wait_until" method.

When I have tried this method, I got into another error,
Failure/Error: wait_until do
     NoMethodError:
       undefined method `wait_until' for #
     # ./spec/features/commets_spec.rb:41:in `block (3 levels) in '
While googled more, I found strong discussion of Jonas Nicklas and found that wait_until is removed from capybara 2.0.0 ;(.

Alternatively, I found how to implement "wait_until" back into capybara.

But in same discussion Jonas Nicklas has posted a link of his blog post where he has explained about why this decision was reached. Also he has mentioned how we can wait for ajax call to modify DOM.

So, my spec changed to,
    scenario "can create comment" do
      user =  FactoryGirl.create(:user, :email => "p@abc.com")
      post = FactoryGirl.create(:published_post)

      login(user)

      visit post_path(post)

      page.find('#comment_body').set("This is my comment")
      keypress_script = "var e = $.Event('keypress', { keyCode: 13 }); $('#comment_body').trigger(e);"
      page.execute_script(keypress_script)
   
      page.should have_selector(".comment", :text => "This is my comment")
      page.should have_link('Reply')                         # Subsequent call also passed due to have_selector
      page.should have_link('Edit')
      page.should have_link('Destroy')
    end
 
and which is passing. ;)

References :
https://groups.google.com/forum/#!topic/ruby-capybara/qQYWpQb9FzY
http://www.elabs.se/blog/53-why-wait_until-was-removed-from-capybara
https://gist.github.com/KevinTriplett/5087744