Tuesday, March 5, 2013

Rails : Code : Split Records

I was very much impressed when I saw the split entry functionality in time-sheet project we were using. I wanted to try out same in ROR. But I will also try this out in PHP now. First we will look at ROR code.

Assume that we are creating a time-sheet project and we need to split the duration and considering, we already have the rails project with authentication, and now we generate a new section 'Time Records', which will store the records of users with duration.
I am running scaffold so we will have all views, controller and model.
 rails g scaffold time_records description:text duration:float parent_id:integer


Run migration to add table in database.
 rake db:migrate
Now we will add new record



This is a basic setup what we need till now. Once this base is complete we will start with actual requirement. Now first we need to add a anchor tag (link) with which the functionality will work. SO I have added it in index page.



In this example I am using javascript function for ajax call but it is not complusory, we can directly use send the user to the form page with out javascript. So I am using onclick attribute for calling a function.
<%= link_to 'Split', '#', :onClick => "split_duration_for_entry('split_#{time_record.id}', #{time_record.id})" %>
In that function
/* this is for spliting the duration */
function split_duration_for_entry( div, id ){
  var divid = "#"+div
  var classes = jQuery(".split_box");

  jQuery(classes).each(function(){
    var thisid = $(this).parent('td').attr('id')
    var v = jQuery("#"+thisid).next().val();
    jQuery("#"+thisid).html("<a href='#' onClick=\"split_duration_for_entry('"+thisid+"', "+id+")\">Split</a>")
  });

  actual_duration = $('#split_'+id).prev('td').html();
  jQuery.ajax({
    type: 'GET',
    url: '/time_records/split_entries_div',
    data:{'record_id' : id, 'actual_duration': actual_duration},
    success: function(transform){
      jQuery(divid).html(transform);
    }
  });
}

So this function will
  • Hide opened split duration divs if open.
  • Send the parameters(id and duration) to split_entries_div action in our controller.
  • Once the action is called display the partial with data and links.

In controller
def split_entries_div
  id = params[:record_id].to_i
  time_record = TimeRecord.find(id)
  render partial: "split_entries", locals: {record_id: id, old_duration: time_record.duration.to_f}, layout: false
end 
So here comes the partial.
  
<div class="split_box">
  <div>
    <%= hidden_field_tag "actual_duration#{record_id}", params[:actual_duration] %>
    <%= text_field_tag "old_duration_#{record_id}", params[:actual_duration], size: 5 %>
    <%= text_field_tag "new_duration_#{record_id}", 0, size: 5 %>
  </div>
  <div>
    <%= link_to 'Split', "#this", onClick: "split_entries(this, #{record_id})" %>
    <%= link_to "Cancel", "#this", onClick: "cancel_split_entry(this, #{record_id})" %>
  </div>
</div>
In CSS
.split_box{
  background: none repeat scroll 0 0 #EFEFEF;
  border: 4px solid #CCCCCC;
  width: 100px;
}

In this partial we will have
  • 2 main text fields which will contain the old duration for updating the original record and new duration for which we will be creating a new record.
  • 1 hidden field to keep the original duration so if the validation is called then we can again store the original duration value to old duration field.
  • 2 anchor tags for calling actual functionality and canceling the div poped up.



First the main functionality. Actual spliting/ updating the entry will be performed if the user clicks the split link. On which 'split_entries' function is called by passing record's id.

function split_entries( id ){
  var newrecrd;
  var new_record_val = jQuery("#new_duration_"+id).val();
  var old_record_val = jQuery("#old_duration_"+id).val();
  
  var actual = $("#actual_duration"+id).val();
  var total = parseFloat(old_record_val) + parseFloat(new_record_val);
  
  if(parseFloat(actual) < total ) {
    alert("Total duration for OLD and New records can not be greater than original value");
    jQuery("#old_duration_"+id).val(actual);
    jQuery("#new_duration_"+id).val(0);
    return false; 
  }else if(parseFloat(old_record_val) < 24){
    if(new_record_val==0 || new_record_val==""){
      newrecrd = false
    }else{
      newrecrd = true
    }

    cancel_split_entry(id);

    jQuery.ajax({
      type: 'GET',
      url: '/time_records/split_entries',
      data:{
        'newrecrd' : newrecrd,
        'old_id' : id,
        'old_duration': parseFloat(old_record_val).toFixed(2),
        'new_record_val' : parseFloat(new_record_val).toFixed(2)
      },
      success: function(transport){
        window.location.reload();
      }
    });
  }else {
    alert("Duration can not be greater than 24 hrs. Enter valid duration");
    return false;
  }
}

I have added some validations to this functionality
  • Duration can not be greater than 24 hours OR
  • The total of both value can not be greater that the original duration
We can just remove/ modify these validation as per our requirement

Other than that
  • If it passes the validation it will first check if the new duration value should not be blank or equal to 0. This will set true or false to local variable 'newrecrd' which will be passes to the action as parameter via ajax.
  • Original record is will be passes and old_id.
  • Both new duration and old duration will be passed to the action.
  
# split duration
  def split_entries
    @time_record = TimeRecord.find(params[:old_id].to_i)
    description = @time_record.description
    @time_record.update_attributes(description: description, duration: params[:old_duration])
    if params[:newrecrd]=='true'
      TimeRecord.create(description: description, duration: params[:new_record_val], parent_id: @time_record.id)
    end
    redirect_to :back
  end
In which get the original record and update the duration
  • if params[:newrecrd] is true then create a new record with new duration value.
And now in routes add
resources :time_records do 
  collection do 
    get 'split_entries'
    get 'split_entries_div'
  end
end



Once the set up is done restart the application and check the output


Get the code at Github : Split-Records

1 comment: