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

324

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

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

например:

 @newrecord=Record.copy(:id)  *perhaps?*
Теги:
rails-activerecord

8 ответов

549
Лучший ответ

Чтобы получить копию, используйте метод клонирования (или 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 .
  • 1
    Документы Rails 3.1 по clone говорят, что он все еще работает, но я использую Rails 3.1.0.rc4 и даже new? метод не работает.
Показать ещё 9 комментариев
61

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

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, вам нужно будет явно вызвать save, поэтому, если вы хотите, чтобы он был сохранен автоматически, измените Task.new на Task.create.

Мир.

  • 5
    Не совсем уверен, насколько это хорошая идея WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at вы получите WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at удается WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at возвращен
  • 0
    Отличное объяснение.
Показать ещё 4 комментария
31

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

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

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

не забудьте проверить Amoeba Documentation, но использование довольно просто...

только

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

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

  • 0
    Отличный ответ. Спасибо за детали!
  • 0
    Спасибо, это работает!! Но у меня есть один вопрос, как мне добавить новые записи с клонированием перед сохранением клонированного объекта?
Показать ещё 1 комментарий
26

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

  • 0
    .dup не работает с рельсами 3.1 с ruby 2
  • 1
    @Thorin согласно принятому ответу выше, похоже, что правильный метод для Rails <3.1 - это .clone
17

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

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
  • 0
    Когда я делаю это, я получаю unknown attribute ошибку unknown attribute с одним столбцом из-за столбца, который существует из-за отношения has_many. Есть ли способ обойти это?
  • 0
    с rails4 он не создает уникальный идентификатор для записи
Показать ещё 2 комментария
8

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

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

Легко:

#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

Вы также можете проверить драгоценный камень acts_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">

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню