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


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. Я пытался выполнить этот код, но он не работал здесь.
Виктор


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 .


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

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     

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">

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


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'}

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.