3

Is it possible to create multiple rows for one record based off two different column values?

Example: In my table for my Model Entry I have a column called full_range this will have the dates choose between a leave_start and leave_end date, so for example lets say I pick 05/01/15 as my leave_start date and 05/05/15 as my leave_end date, this would give me 05/01/15, 05/02/15, 05/03/15, 05/04/15, 05/05/15 for my full_range.

Then for my other column called range_days the value will be 5 this is because I have 5 days between 05/01/15 and 05/05/15.

What I would like to do is split my full_range values based off range_days and I would like to insert multiple rows for each date from my full range and I guess the range_days would come into play to say create the value of rows to create.

Right now I only get one row like so..

ID     Created_at Full_range_date   emp_id    range_days  leave_start leave_end   full_range     
10686   1-May-15    5/1/2015         TEST1        5        05/01/15   05/05/15     05/01/15 05/02/15 05/03/15 05/04/15 05/05/15     

So in theory what I would like to see in my database would be this so it looks at full_range first and grabs the first date fills it in for full_range_date then looks at the next and next... based of range_days it does 5days which is 5 rows.

ID     Created_at Full_range_date   emp_id    range_days  leave_start leave_end   full_range     
10686   1-May-15    5/1/2015         TEST1        5        05/01/15   05/05/15     05/01/15 05/02/15 05/03/15 05/04/15 05/05/15     
10687   1-May-15    5/2/2015         TEST1        5        05/01/15   05/05/15     05/01/15 05/02/15 05/03/15 05/04/15 05/05/15     
10688   1-May-15    5/3/2015         TEST1        5        05/01/15   05/05/15     05/01/15 05/02/15 05/03/15 05/04/15 05/05/15     
10689   1-May-15    5/4/2015         TEST1        5        05/01/15   05/05/15     05/01/15 05/02/15 05/03/15 05/04/15 05/05/15     
10690   1-May-15    5/5/2015         TEST1        5        05/01/15   05/05/15     05/01/15 05/02/15 05/03/15 05/04/15 05/05/15     

How could I go about doing this any help would be greatly appreciated!!!

I'm using rails 4.1.8

also for extra info here is my entry controller.

class EntryController < ApplicationController



  def new
    @entry = Entry.new

    respond_to do |format|

      format.html# new.html.haml
      format.xml { render :xml => @entry }
   end
 end


  def create
    params.permit!
    @entry = Entry.new(params[:entry])
    @entry.t_d
    @entry.day_hours
    @entry.current_user = current_user

    respond_to do |format|

      if @entry.save
        if current_user.email.nil?
          @entry.create_totals
          format.html { redirect_to(entry_path( @entry ), :notice => 'Entry successfully created, but you will not recieve any notifications, because you email is blank!') }
          format.xml { render :xml => @entry, :status => :created, :location => @entry }
        else    
          @entry.create_totals
          EntryMailer.submit_for_approval(@entry).deliver
          format.html { redirect_to(entry_path( @entry ), :notice => 'Entry successfully created.') }
          format.xml { render :xml => @entry, :status => :created, :location => @entry }
        end 
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @entry.errors, :status => :unprocessable_entity }
      end
    end
  end

and my entry model

class Entry < ActiveRecord::Base

  self.primary_key = 'id' 
end 

Based off the answer given I tried this but still it only created one row what Would like it to do is create a new row for each date from full_range

 def create
   params.permit!
   @entry = Entry.new(params[:entry])
   @entry.t_d
   @entry.day_hours
   @entry.current_user = current_user

   # send my email
   respond_to do |format|

     begin
       Entry.transaction do
         @entry.full_range.split(' ').each do |date|
           entry = Entry.new( @entry.attributes.to_options )
           entry.full_range = date
           entry.save!
         end
       end

       if current_user.email.nil?
         @entry.create_totals
         format.html { redirect_to(entry_path( @entry ), :notice => 'Entry successfully created, but you will not recieve any notifications, because you email is blank!') }
         format.xml { render :xml => @entry, :status => :created, :location => @entry }
       else
         @entry.create_totals
         EntryMailer.submit_for_approval(@entry).deliver
         format.html { redirect_to(entry_path( @entry ), :notice => 'Entry successfully created.') }
         format.xml { render :xml => @entry, :status => :created, :location => @entry }
       end

     rescue
       format.html { render :action => "new" }
       format.xml  { render :xml => @entry.errors, :status => :unprocessable_entity }
     end
   end
 end

Just for giggles I tried just doing a raw sql statement like in my entry model

like so this inserted the correct amount of rows but all of the data was an exact match nothing changed.

  def trying_it_all
    @id = self.id
    @leave_end = self.leave_end.to_date
    @leave_start = self.leave_start.to_date
    @range_vals = self.range_days
    if !(self.range_days == 0)  
       range_days.times do 
      sql = "insert into entry values('#{@id}', '#{@c_d}', '#{@c_u}', '#{@emp_id}', '#{@range_vals}', 'N')"
      Entry.connection.execute(sql)
    end
  end
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

1 Answers1

1

This is absolutely possible, and there are a number of things to consider in your implementation. The brunt of the will be in your #create action. You have all the information you need already, so here's how I would personally approach it.

First of all, when creating multiple records at once, always use a transaction. If you're not familiar, the idea is that your changes will only be made to the database if every record saves successfully (so if one of the records fails, it will rollback all others, preventing your data from becoming inconsistent). An implementation could look something like this:

  def create
    params.permit!
    @entry = Entry.new(params[:entry])
    @entry.t_d
    @entry.day_hours
    @entry.current_user = current_user

    respond_to do |format|

      if # Condition here

        ActiveRecord::Base.transaction do
          # Create your records here
        end

      else
        # Indicate failure
      end

    end
  end

Now, you may observe that there is no condition listed for the if statement. That's because with multiple transactions we'll need a better way to react to whether or not they succeed than just the result of @entry.save. The easiest way to do this is with a Begin/Rescue block (which you may recognize as Try/Catch from more mainstream languages). It would look like this:

respond_to do |format|

  begin       
    ActiveRecord::Base.transaction do
      # Create your records here
    end
  rescue
    # Indicate failure
  end

end

The way this works is the Begin block will execute, and the transaction will begin. Inside the transaction, if something fails we'll raise an error. The entire transaction will rollback, and the error will jump us out of the Begin block, and into the Rescue block instead.

Now, within the transaction, we need to create multiple records. This part should be fairly straightforward. We'll need to use a loop to create a number of records based upon the range_days. Inside the transaction we'll want to do something like this:

(1..@entry.range_days).each do
  entry = Entry.new( @entry.attributes.to_options )
  entry.full_range_date = # Calculation to determine the date of this entry
  entry.save!
end

This will create one entry for each day in range_days. The first line in the loop makes a non-instance variable with the same values as @entry. The second line is where you'll change the value of full_range_date. The third line uses the .save! function, which has an important difference compared to the .save function; it will raise an error if it fails. This is the trigger that will allow you to escape the Begin block and jump to the Rescue block if anything goes horribly wrong.

Regarding your calculations for setting the new full_range_date, this will either involve date functions or string manipulation (depending on how you handle dates). See my REVISION at the bottom of the answer for an idea on how to accomplish this. So essentially, your create function could look a lot like this:

def create
    params.permit!
    @entry = Entry.new(params[:entry])
    @entry.t_d
    @entry.day_hours
    @entry.current_user = current_user

    respond_to do |format|

      begin       
        ActiveRecord::Base.transaction do
          (1..@entry.range_days).each do
            entry = Entry.new( @entry.attributes.to_options )
            entry.full_range_date = # Calculation to determine the date of this entry
            entry.save!
          end
        end

        if current_user.email.nil?
          @entry.create_totals
          format.html { redirect_to(entry_path( @entry ), :notice => 'Entry successfully created, but you will not recieve any notifications, because you email is blank!') }
          format.xml { render :xml => @entry, :status => :created, :location => @entry }
        else    
          @entry.create_totals
          EntryMailer.submit_for_approval(@entry).deliver
          format.html { redirect_to(entry_path( @entry ), :notice => 'Entry successfully created.') }
          format.xml { render :xml => @entry, :status => :created, :location => @entry }
        end 

      rescue
        format.html { render :action => "new" }
        format.xml  { render :xml => @entry.errors, :status => :unprocessable_entity }
      end

    end
end

Closing Comment

params.permit! is an incredibly tempting method to use, in that it stops you from having to worry about strong parameters, or the need to update your controller when your model changes... but it's incredibly dangerous. Not only does it possible for a user to pass you fields you don't expect (thus forcing you to handle volumes of data for which you aren't prepared), it also allows hidden field to be defined by a savvy user. For instance, if I sent a POST request to your entry, I could specify { :Created_At => 1-Jan-2015 }, making it look like I created this record four months earlier, and completely devaluing that column. In this case that's not major, but imagine you had a User model with an :is_administrator field. Anyone could then create users with administrative rights. This may be worth reading, if you're interested.

REVISED

Quick addendum! If you already have the Full_range_date values in your full_range variable, you could replace the original loop with this:

@entry.full_range.split(' ').each do |date|
  entry = Entry.new( @entry.attributes.to_options )
  entry.full_range_date = date
  entry.save!
end

This will turn full_range value into an array, iterate through each element, and set the value of full_range_date for you, with no further calculations.

Community
  • 1
  • 1
ConnorCMcKee
  • 1,625
  • 1
  • 11
  • 23
  • I tried this but, still it is only making one row.. Thank you for all the helpful stuff btw!!! @ConnorCMcKee – Snowman1234 May 01 '15 at 16:43
  • Could you add a revision to your question with exactly what your create action looks like now? It would also be potentially helpful if you could show exactly what parameters you passed to it, and what record was created. Thanks! – ConnorCMcKee May 01 '15 at 17:32
  • Thank you! The only part I don't see is exactly what values you passed to it, and what was created. This would be helpful because then we could see exactly what's happening. Alternatively, you could try using the `debug` command to see what values are being handled in the loop. The ID is not being passed as a parameter, is it? If so, then instead of creating new records we would simply be updating the same one. – ConnorCMcKee May 01 '15 at 18:24