Какой самый простой способ дублировать запись активной записи?

412

Я хочу сделать копию записи активной записи, меняя одно поле в процессе (в дополнение к идентификатору ). Какой самый простой способ сделать это?

Я понимаю, что могу создать новую запись, а затем перебрать каждое из полей, копируя данные поле за полем - но я подумал, что должен быть более простой способ сделать это ...

такие как:

 @newrecord=Record.copy(:id)  *perhaps?*
казарка
источник

Ответы:

622

Чтобы получить копию, используйте метод клонирования (или dup для rails 3.1+):

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Затем вы можете изменить любые поля, которые вы хотите.

ActiveRecord переопределяет встроенный клон Object #, чтобы предоставить вам новую (не сохраненную в БД) запись с неназначенным идентификатором.
Обратите внимание, что он не копирует ассоциации, поэтому вам придется делать это вручную, если вам нужно.

Клон Rails 3.1 является мелкой копией, вместо этого используйте dup ...

Майкл Сепкот
источник
6
Это все еще работает в Rails 3.1.0.beta? Когда я это сделаю q = p.clone, и тогда p == qя trueвернусь. С другой стороны, если я использую q = p.dup, я falseвозвращаюсь при сравнении их.
Autumnsault
1
Документы Rails 3.1 по clone говорят, что он все еще работает, но я использую Rails 3.1.0.rc4, и даже этот new?метод не работает.
Турадг
12
Похоже, что эта функциональность была заменена на dup: gist.github.com/994614
skattyadz
74
Определенно НЕ используйте клон. Как уже упоминалось другими авторами, метод clone теперь делегирует использование Kernel # clone, который будет копировать идентификатор. Теперь используйте ActiveRecord :: Base # dup
bradgonesurfing
5
Я должен сказать, что это была настоящая боль. Такое простое изменение намеченной функциональности может нанести ущерб некоторым важным функциям, если у вас не было хорошего покрытия спецификаций.
Мэтт Смит
74

В зависимости от ваших потребностей и стиля программирования вы также можете использовать комбинацию нового метода класса и слияния. Ввиду отсутствия более простого примера, предположим, что у вас есть задача, запланированная на определенную дату, и вы хотите дублировать ее на другую дату. Фактические атрибуты задачи не важны, поэтому:

old_task = Task.find (task_id)
new_task = Task.new (old_task.attributes.merge ({: schedule_on => some_new_date}))

создаст новую задачу с :id => nil, :scheduled_on => some_new_dateи все другие атрибуты будут такими же, как и исходная задача. Используя Task.new, вам придется явно вызывать функцию save, поэтому, если вы хотите, чтобы она сохранялась автоматически, измените Task.new на Task.create.

Мир.

Филипп Кеббе
источник
5
Не совсем уверен, насколько это хорошая идея, WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at
потому что
Когда я делаю это, я получаю неизвестную ошибку атрибута с одним столбцом из-за столбца, который существует из-за отношения has_many. Есть ли способ обойти это?
Рубен Мартинес-младший
2
@RubenMartineJr. Я знаю, что это старый пост, но да, вы можете обойти это, используя «.except» в хэше атрибутов: new_task = Task.new (old_task.attributes.except (: attribute_you_dont_want,: another_aydw) .merge ({: schedule_on => some_new_date}))
Ninigi
@PhillipKoebbe спасибо - но что, если я хочу, чтобы идентификатор не был нулевым? Я хочу, чтобы rails автоматически назначали новый идентификатор при создании дубликата - возможно ли это?
BKSpurgeon
1
old_task.attribtes, к сожалению, также назначает поле идентификатора. Это не работает для меня
BKSpurgeon
32

Вам также может понравиться драгоценный камень Amoeba для ActiveRecord 3.2.

В вашем случае, вы , вероятно , хотите, чтобы использовать nullify, regexили prefixопций , доступных в DSL конфигурации.

Он поддерживает простое и автоматическое рекурсивное дублирование has_one, has_manyи has_and_belongs_to_manyассоциацию, поле предварительной обработку и очень гибкую и мощную конфигурацию DSL , которые могут быть применены как к модели и на лета.

Обязательно ознакомьтесь с документацией Amoeba, но ее использование довольно просто ...

просто

gem install amoeba

или добавить

gem 'amoeba'

в ваш Gemfile

затем добавьте блок амебы в вашу модель и запустите dupметод как обычно

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    enable
  end
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

class Tag < ActiveRecord::Base
  has_and_belongs_to_many :posts
end

class PostsController < ActionController
  def some_method
    my_post = Post.find(params[:id])
    new_post = my_post.dup
    new_post.save
  end
end

Вы также можете контролировать, какие поля копируются различными способами, но, например, если вы хотите предотвратить дублирование комментариев, но хотите сохранить те же теги, вы можете сделать что-то вроде этого:

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    exclude_field :comments
  end
end

Вы также можете предварительно обработать поля, чтобы помочь указать уникальность как с префиксами и суффиксами, так и с регулярными выражениями. Кроме того, есть также множество опций, поэтому вы можете писать в наиболее удобном для вас стиле:

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    include_field :tags
    prepend :title => "Copy of "
    append :contents => " (copied version)"
    regex :contents => {:replace => /dog/, :with => "cat"}
  end
end

Рекурсивное копирование ассоциаций легко, просто включите амебу на дочерних моделях.

class Post < ActiveRecord::Base
  has_many :comments

  amoeba do
    enable
  end
end

class Comment < ActiveRecord::Base
  belongs_to :post
  has_many :ratings

  amoeba do
    enable
  end
end

class Rating < ActiveRecord::Base
  belongs_to :comment
end

Конфигурационный DSL имеет еще больше опций, поэтому обязательно ознакомьтесь с документацией.

Наслаждайтесь! :)

Вон Драугон
источник
Отличный ответ. Спасибо за детали!
Дерек Приор
Спасибо, это работает!! Но у меня есть один вопрос, как мне добавить новые записи с клонированием перед сохранением клонированного объекта?
Мохд Анас
1
Просто исправление здесь. Правильный метод .amoeba_dupне только .dup. Я пытался выполнить этот код, но он не работал здесь.
Виктор
31

Используйте ActiveRecord :: Base # dup, если вы не хотите копировать идентификатор

bradgonesurfing
источник
1
@ Торин согласно принятому ответу выше, похоже, что правильный метод для Rails <3.1.clone
Дэн Уивер
24

Я обычно просто копирую атрибуты, меняя все, что мне нужно:

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
Франсуа Босолей
источник
Когда я делаю это, я получаю unknown attributeошибку с одним столбцом из-за столбца, который существует из-за отношения has_many. Есть ли способ обойти это?
Рубен Мартинес мл
с rails4 он не создает уникальный идентификатор для записи
Ben
4
Чтобы создать новую запись с помощью Rails 4, выполните User.create(old_user.attributes.merge({ login: "newlogin", id: nil })). Это сохранит нового пользователя с правильным уникальным идентификатором.
РаджеМ
В Rails есть Hash # кроме и Hash # slice , что делает предлагаемый метод наиболее мощным и менее подверженным ошибкам. Не нужно добавлять дополнительные библиотеки, легко расширять.
Kucaahbe
10

Если вам нужна глубокая копия с ассоциациями, я рекомендую гем deep_cloneable .

raidfive
источник
Я тоже. Я попробовал этот драгоценный камень, и он работал впервые, очень прост в использовании.
Роб
4

В Rails 5 вы можете просто создать дублированный объект или запись, подобную этой.

new_user = old_user.dup
Форам Такрал
источник
2

Самый простой способ:

#your rails >= 3.1 (i was done it with Rails 5.0.0.1)
  o = Model.find(id)
 # (Range).each do |item|
 (1..109).each do |item|
   new_record = o.dup
   new_record.save
 end

Или

# if your rails < 3.1
 o = Model.find(id)
 (1..109).each do |item|
   new_record = o.clone
   new_record.save
 end     
ThienSuBS
источник
2

Вот пример переопределения #dupметода ActiveRecord для настройки дублирования экземпляра и включения дублирования отношений:

class Offer < ApplicationRecord
  has_many :offer_items

  def dup
    super.tap do |new_offer|

      # change title of the new instance
      new_offer.title = "Copy of #{@offer.title}"

      # duplicate offer_items as well
      self.offer_items.each { |offer_item| new_offer.offer_items << offer_item.dup }
    end
  end
end

Примечание: этот метод не требует внешнего гема, но требует более новой версии ActiveRecord с #dupреализованным методом

Зоран Майсторович
источник
0

Вы также можете проверить драгоценный камень act_as_inheritable .

«Acts As Inheritable - это Ruby Gem, специально созданный для моделей Rails / ActiveRecord. Он предназначен для использования с само-ссылочной ассоциацией или с моделью, имеющей родителя, который разделяет наследуемые атрибуты. Это позволит вам наследовать любой атрибут или отношение от родительской модели ".

Добавляя acts_as_inheritableк своим моделям, вы будете иметь доступ к этим методам:

inherit_attributes

class Person < ActiveRecord::Base

  acts_as_inheritable attributes: %w(favorite_color last_name soccer_team)

  # Associations
  belongs_to  :parent, class_name: 'Person'
  has_many    :children, class_name: 'Person', foreign_key: :parent_id
end

parent = Person.create(last_name: 'Arango', soccer_team: 'Verdolaga', favorite_color:'Green')

son = Person.create(parent: parent)
son.inherit_attributes
son.last_name # => Arango
son.soccer_team # => Verdolaga
son.favorite_color # => Green

inherit_relations

class Person < ActiveRecord::Base

  acts_as_inheritable associations: %w(pet)

  # Associations
  has_one     :pet
end

parent = Person.create(last_name: 'Arango')
parent_pet = Pet.create(person: parent, name: 'Mango', breed:'Golden Retriver')
parent_pet.inspect #=> #<Pet id: 1, person_id: 1, name: "Mango", breed: "Golden Retriver">

son = Person.create(parent: parent)
son.inherit_relations
son.pet.inspect # => #<Pet id: 2, person_id: 2, name: "Mango", breed: "Golden Retriver">

Надеюсь, это поможет вам.

esbanarango
источник
0

Поскольку при дублировании модели может быть больше логики, я бы предложил создать новый класс, в котором вы обрабатываете всю необходимую логику. Чтобы облегчить это, есть драгоценный камень, который может помочь: клоун

В соответствии с их примерами документации для модели User:

class User < ActiveRecord::Base
  # create_table :users do |t|
  #  t.string :login
  #  t.string :email
  #  t.timestamps null: false
  # end

  has_one :profile
  has_many :posts
end

Вы создаете свой класс клонеров:

class UserCloner < Clowne::Cloner
  adapter :active_record

  include_association :profile, clone_with: SpecialProfileCloner
  include_association :posts

  nullify :login

  # params here is an arbitrary Hash passed into cloner
  finalize do |_source, record, params|
    record.email = params[:email]
  end
end

class SpecialProfileCloner < Clowne::Cloner
  adapter :active_record

  nullify :name
end

и затем используйте это:

user = User.last
#=> <#User(login: 'clown', email: 'clown@circus.example.com')>

cloned = UserCloner.call(user, email: 'fake@example.com')
cloned.persisted?
# => false

cloned.save!
cloned.login
# => nil
cloned.email
# => "fake@example.com"

# associations:
cloned.posts.count == user.posts.count
# => true
cloned.profile.name
# => nil

Пример скопирован из проекта, но он даст четкое представление о том, чего можно достичь.

Для быстрой и простой записи я бы пошел с:

Model.new(Model.last.attributes.reject {|k,_v| k.to_s == 'id'}

Пауло Фидальго
источник