Какой самый простой способ дублировать запись активной записи? - PullRequest
391 голосов
/ 13 сентября 2008

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

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

, таких как:

 @newrecord=Record.copy(:id)  *perhaps?*

Ответы [ 11 ]

596 голосов
/ 13 сентября 2008

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

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

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

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

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

68 голосов
/ 15 сентября 2008

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

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

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

Мир.

32 голосов
/ 28 февраля 2012

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

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

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

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

только

gem install amoeba

или добавить

gem 'amoeba'

в ваш Gemfile

затем добавьте блок amoeba в вашу модель и запустите метод 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 есть еще больше опций, поэтому обязательно ознакомьтесь с документацией.

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

30 голосов
/ 05 августа 2011

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

21 голосов
/ 15 сентября 2008

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

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
9 голосов
/ 01 сентября 2011

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

2 голосов
/ 04 января 2019

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

new_user = old_user.dup
2 голосов
/ 30 октября 2016

Простой способ:

#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     
0 голосов
/ 20 августа 2018

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

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 голосов
/ 16 марта 2018

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

В соответствии с их примерами документации для модели 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'}

...