Greg
Greg

Reputation: 2609

rails_to for a duplicate record

The following works fine in console and does what I want (the first line would do it, the others were just to simulate the changes. Actually it was real, those were the only changes needed):

irb(main):012:0> year = Year.find(678).dup
  Year Load (0.4ms)  SELECT "years".* FROM "years" WHERE "years"."id" = $1 ORDER BY "years"."year_date" ASC LIMIT $2  [["id", 678], ["LIMIT", 1]]
=> #<Year id: nil, year_date: "1905-09-01", created_at: nil, updated_at: nil, resto: false, resid: true, file_basename: nil, person_id: 86, location_id: 95, title: "Resident", notes: "", resto_name: "", aws_url: nil, from_where_url: nil, caption: nil, croatian: true, from_where: nil, doc_id: 66>
irb(main):013:0> year.location_id = 211
=> 211
irb(main):014:0> year.resto = true
=> true
irb(main):015:0> year.resid = false
=> false
irb(main):016:0> year.title = "Co-proprietor"
=> "Co-proprietor"
irb(main):017:0> year.save
   (0.3ms)  BEGIN
  Location Load (0.5ms)  SELECT "locations".* FROM "locations" WHERE "locations"."id" = $1 LIMIT $2  [["id", 211], ["LIMIT", 1]]
  Person Load (0.3ms)  SELECT "people".* FROM "people" WHERE "people"."id" = $1 LIMIT $2  [["id", 86], ["LIMIT", 1]]
  Doc Load (0.2ms)  SELECT "docs".* FROM "docs" WHERE "docs"."id" = $1 LIMIT $2  [["id", 66], ["LIMIT", 1]]
  Year Create (5.3ms)  INSERT INTO "years" ("year_date", "created_at", "updated_at", "resto", "resid", "person_id", "location_id", "title", "notes", "resto_name", "croatian", "doc_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING "id"  [["year_date", "1905-09-01"], ["created_at", "2020-05-10 15:12:37.102224"], ["updated_at", "2020-05-10 15:12:37.102224"], ["resto", true], ["resid", false], ["person_id", 86], ["location_id", 211], ["title", "Co-proprietor"], ["notes", ""], ["resto_name", ""], ["croatian", true], ["doc_id", 66]]
   (40.9ms)  COMMIT
=> true

I, of course, would do the changes in an edit or new page. I just want that page created with a duplicate of the information from the show page I'm on. Use case is creating a bunch of record and many are almost duplicates, but does need to be done manually because there is no pattern to the information that changes.

I've created various buttons, but nothing works

<%= link_to 'Duplicate this connection (FIXME)', new_year_path(@year.dup), action: dup %>

with:

def dup
  @year = Year.find(params[:id]).dup
end

Another iteration: <%= link_to 'Duplicate this connection (FIXME)', edit_year_path(@year.dup) %> No route matches {:action=>"edit", :controller=>"years", :id=>nil}, missing required keys: [:id]

I'm lost on this, but probably not that hard.

Upvotes: 0

Views: 547

Answers (3)

Amit Patel
Amit Patel

Reputation: 15985

Search the record for which you want to duplicate, call attributes to get all attributes key-value, discard timestamp columns and pass it to create method to create record.

def dup
  year = Year.find(params[:id])

  @year = Year.create!(year.attributes.except("created_at", "updated_at"))
end

Upvotes: 1

max
max

Reputation: 101811

resources :years do
  get :duplicate
end
class Year < ApplicationRecord
  # creates a new instance of year with a a subset of the attributes
  # @return [Year] 
  def duplicate
    # attributes returns a hash with string keys 
    whitelist = %w( foo bar baz )
    self.class.new(attributes.slice(*whitelist))
  end
end
class YearsController < ApplicationController

  # ...

  # GET /years/:id/duplicate 
  def duplicate
    @year = Year.find(:id).duplicate 
    render :new
  end

  # ...
end

Its probably a good idea to whitelist the attributes you want to copy instead of blacklisting.

Upvotes: 1

Andrew E.
Andrew E.

Reputation: 83

When you call #.dup on an ActiveRecord model object, which you're doing here, it copies over all attributes except for id and the base timestamps. What this means is that you have an unpersisted object. This is why you are getting the exception messages you are getting.

Assuming you want to duplicate record 678, let's say, I'd expect a path like this:

/years/new?base_id=678

In the above, base_id=678 is a query string parameter.

You'd generate it like this:

<%= link_to "Duplicate", new_year_path(base_id: @year&.id) %>

(assuming @year is initialized, of course)

Then, in your controller action:

def new
  @year = Year.find_by(id: params[:base_id])&.dup || Year.new
end

Assuming we find the Year record in question, we duplicate it. Otherwise, we fall back to a new Year object and life is fine.

This should resolve your issue.

Upvotes: 1

Related Questions