Wednesday 26 March 2014

Ruby Hash Selector Pattern

If you are building some web applications, in some scenario you should route or redirect depends upon your logical conditions. There is so many ways to put your logical conditions on the code. For example most of the people will use either if..else or switch..case statement(in Ruby). Anyway result is same only but execution time will vary depends upon which way you are implementing the stuff. In this article, we are going to learn how to use Hash selector pattern for logical options.

Let's take first one using if.. else selector pattern.

if params[:game_type] == :cricket
  "playing cricket"
elsif params[:game_type] == :football
  "playing football"
elsif params[:game_type] == :hockey
  "playing hockey"
elsif params[:game_type] == :tabletennis
  "playing tabletennis"
elsif params[:game_type] == :basketball
  "playing basketball"
elsif params[:game_type] == :volleyball
  "playing volleyball"
end

But above code looks bit ugly. we can achieve same result using switch/case also. Lets see how to convert the same code into switch/case selector pattern.

cash params[:game_type]
  when :cricket then "playing cricket"
  when :football then "playing football"
  when :hockey then "playing hockey"
  when :tabletennis then "playing tabletennis"
  when :basketball then "playing basketball"
  when :volleyball then "playing volleyball"
end

Compare if..else, switch/case will be more readable and easy to understand for new readers. Main thing is switch/case pattern will take very less time to execute the code than if..else pattern. so if you want to put more elsif conditions better to use switch statement to get more boost on performance area.

Lets jump into Hash selector pattern, how to achieve the same things
{
:cricket => "playing cricket",
:football => "playing football",
:hockey => "playing hockey",
:tabletennis => "playing tabletennis",
:basketball => "playing basketball",
:volleyball => "playing volleyball",
}[params[:game_type]]

see Hash pattern selector is more readable than swith/case pattern!!!.

Now we are going to know how to use hash selector pattern in Procs/lamdas

The above example, just printing the message or just assigning value using the hash pattern is not that much special. What about calling methods using that? can we use hash pattern to call methods?. Yes we can definitely do. Lets look at the following example.

May be you have this kind of logic in your controller

if @user.save
  respond_with user_path(@user)
else
  render_error @user

end

The above code we can convert into hash pattern in the following way,

{
 true =>  -> {self.respond_with user_path(@user)},
 false => -> {self.render_error @user}
}[@user.save].call

Conclusion:

Important thing is if you are using hash selector pattern might be your performance will be slow compare switch/case statement. so better to use switch/case statement if you are planning to put more elsif condition scenario. This article to show you how to use hash selector pattern but if you are asking me regarding performance, i wont suggest you to use hash selector patter. Here i have attached benchmark result for your reference in all there pattern( like if..elsif,switch/case and hash selector pattern). Just look the result than you will decide which way you have to go ahead.

All three pattern benchmark results,look at the code snippets

 game_type = :cricket

time = Time.now
1_000_000.times do
msg = case game_type
  when :cricket then 'playing cricket'
  when :football then 'playing football'
  when :hockey then 'playing hockey'
  when :tabletennis then 'playing tabletennis'
  when :basketball then 'playing basketball'
  when :volleyball then 'playing volleyball'
  end
end
puts "case: #{Time.now - time}"


time = Time.now
1_000_000.times do
  msg = if game_type == :cricket
      "playing cricket"
    elsif game_type == :football
      "playing football"
    elsif game_type == :hockey
      "playing hockey"
    elsif game_type == :tabletennis
      "playing tabletennis"
    elsif game_type == :basketball
      "playing basketball"
    elsif game_type == :volleyball
      "playing volleyball"
    end
end
puts "if: #{Time.now - time}"


time = Time.now
1_000_000.times do
  msg = {
    :cricket => "playing cricket",
    :football => "playing football",
    :hockey => "playing hockey",
    :tabletennis => "playing tabletennis",
    :basketball => "playing basketball",
    :volleyball => "playing volleyball",
    }[game_type]
end
puts "hash: #{Time.now - time}"


================================================
case: 0.465138302
if: 0.582026524
hash: 3.581798935
================================================


Hope you can understand how to use Hash selector pattern and how it will impact performance area and all. Will catch you on another article!!!

Tuesday 11 March 2014

How to get rid of "X is invalid" error message in validates_associated method in rails

While i am using validates_associated in my project, it gave "x(some model) name is invalid" error. I will give proper example. Batch is one model, Check is another model and Check invoices is another model. So my active record models looks like as follows.

class Batch < ActiveRecord::Base
  has_many :checks, dependent: :destroy
  has_many :check_invoices, through: :checks
  accepts_nested_attributes_for :checks, allow_destroy: true
  validates_associated :checks
end

class Check < ActiveRecord::Base
  # Associations
  belongs_to :batch
  belongs_to :borrower
  has_many :check_invoices, dependent: :destroy

  accepts_nested_attributes_for :check_invoices, allow_destroy: true
  validates_associated :check_invoices
end

class CheckInvoice < ActiveRecord::Base
  belongs_to :check
end

so my case, while i am trying to save Batch(parent model) user can enter check details as well as check invoices details also.

Problem is, any check model is invalid its giving error messages associated with checks but it gave some unwanted message like "Checks is invalid".  Because validates_associated default message option has "is invalid" string. So this default message will append in your child associated model even though if you define individual level error messages.

Solution 1: Above i explained what is the problem, now i am going to explain how to fix it in global manner/individual model level.


lets create one customer validator file called "my_custom_validates_associated.rb" in config/initializers folder in your rails application.



That file should have following content which you can use anywhere in the models.

module ActiveRecord
  module Validations
    class MyCustomValidatesAssociated< ActiveModel::EachValidator
      def validate_each(record, attribute, value)
        #(value.is_a?(Array) ? value : [value]).each do |v|
        #  unless v.nil?
        #    record.errors.full_messages.each do |msg|
        #      #record.errors.add(:base, "msg")
        #    end
        #  end
        #end
      end
    end

    module ClassMethods
      def own_validates_associated(*attr_names)
        validates_with MyCustomValidatesAssociated, _merge_attributes(attr_names)
      end
    end
  end
end


After adding this file into your config/initializer folder then you have to restart your server then only you will get effect.

Now your model needs little bit change due to our new custom validator added into our application.


Now my own validation associated should work with only checks model then i have to change in Batch model. Now new code looks like as follows.



class Batch < ActiveRecord::Base
  has_many :checks, dependent: :destroy
  has_many :check_invoices, through: :checks
  belongs_to :cash_receipt_source_code ,:foreign_key => "batch_source_code_id"
  accepts_nested_attributes_for :checks, allow_destroy: true
  own_validates_associated :checks
end


Now you wont get "X is invalid/Checks is invalid" default error message in your error messages list.

Solution 2:


The above one is created for generic level. If you want to use only one model then you can filter error messages and remove "X is invalid" message then assign back to other error messages into model.errors.add method. Those as follows for your reference.


  def after_validation
    # you can filter unwanted messages that is not userfriendly.
    final_errors = self.errors.reject{ |err| %w{ user User  }.include?(err.first) }
    self.errors.clear
    final_errors.each { |err| self.errors.add(*err) }
  end
in your model, you can to call the above method on after_validation call back.

Here i attached final output before and after the code change for your understanding.

 Before custom validator how the message is coming with "Checks is invalid"

After we added custom validator called own_validates_associated method,see the desired output error messages.









Hopefully you can enjoy this article. Thanks for reading!!!.

Thursday 6 March 2014

How to retain old text box value using JQuery

In my scenario, i have one table that contains multiple rows, each row will have one checkbox. Once you checked the checkbox , other column(example invoice balance there is some numeric amount column) value has to populate in other text box value which is applied amount column. Once you uncheck the checkbox you should retain what was the old value in applied amount column, may be its came from db.
The following image will show the exact scenario what i am trying to say above.

How to retain old text box value using jquery










After lot of googling, i found a solution in single line of code using pure javascript. But Many people they are achieving same stuff using temp variable. Those are really not needed in my observation. Lets jump into exact solution how to fix it....

<script language="JavaScript">
function check_invoice_row_status(check_box)
{
var current_amount_old_value = $("#"+check_box.id+"").parent().parent().find("input[type='text'][id*='current_amount']").prop("defaultValue");
var adjustment_amount_old_value = $("#"+check_box.id+"").parent().parent().find("input[type='text'][id*='adjustment_amount']").prop("defaultValue");
      $("#"+check_box.id+"").parent().parent().find("input[type='text'][id*='current_amount']").val(current_amount_old_value);
      $("#"+check_box.id+"").parent().parent().find("input[type='text'][id*='adjustment_amount']").val(adjustment_amount_old_value);
}

</script>

Main code which i mention one line code is  $("input[type='text'][id*='current_amount']").prop("defaultValue") ; Will return the old value(db saved value).

Thanks for showing interest to read this article.