Який найпростіший спосіб дублювання запису activerecord?


412

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

Я усвідомлюю, що можу створити нову запис, а потім перегляньте кожне з полів, що копіюють дані по кожному полю - але я зрозумів, що для цього повинен бути простіший спосіб ...

як от:

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

Відповіді:


622

Щоб отримати копію, використовуйте метод клонування (або копію для рейок 3.1+):

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Потім ви можете змінити будь-які поля, які ви хочете.

ActiveRecord переосмислює вбудований клон Object #, щоб дати вам нову (не збережену в БД) запис із непризначеним ідентифікатором.
Зауважте, що він не копіює асоціації, тому вам доведеться робити це вручну, якщо вам потрібно.

Клон Rails 3.1 - це неглибока копія, замість цього використовуйте дуб ...


6
Це все ще працює в Rails 3.1.0.beta? Коли я це роблю q = p.clone, а потім p == q, trueповертаюся. З іншого боку, якщо я використовую q = p.dup, я falseповертаюся, коли порівнюю їх.
Восени

1
Документи Rails 3.1 про клон кажуть, що він все ще працює, але я використовую Rails 3.1.0.rc4 і навіть new?метод не працює.
Турадг

12
Схоже, ця функціональність була замінена на dup: gist.github.com/994614
skattyadz

74
Однозначно НЕ використовуйте клон. Як і інші плакати згадували метод клонування, тепер делегується використовувати клон Kernel #, який буде копіювати ідентифікатор. Використовуйте ActiveRecord :: Base # dup відтепер
bradgonesurfing

5
Треба сказати, це був справжній біль. Проста зміна подібної функції до призначеної функціональності може калічити деякі важливі функції, якщо ви не мали хорошого технічного покриття.
Метт Сміт

74

Залежно від ваших потреб та стилю програмування, ви також можете використовувати комбінацію нового методу класу та злиття. Якщо немає кращого простого прикладу, припустимо, у вас заплановане завдання на певну дату, і ви хочете скопіювати його на іншу дату. Фактичні атрибути завдання не важливі, тому:

old_task = Task.find (task_id)
new_task = Task.new (old_task.attributes.merge ({: призначений_on => some_new_date}))

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

Мир.


5
Не зовсім впевнений, наскільки це гарна ідея, коли ви WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_atповернетесь
bcackerman

Коли я це роблю, я отримую невідому помилку атрибута з одним стовпцем через стовпець, який є через зв’язок has_many. Чи є спосіб обходити це?
Рубен Мартинес-молодший

2
@RubenMartineJr. Я знаю, що це стара публікація, але так, ви можете її обійти, скориставшись ".except" для атрибутів хеш: new_task = Task.new (old_task.attributes.except (: attribute_you_dont_want,: Another_aydw) .merge ({: призначений_on => some_new_date}))
Нінігі

@PhillipKoebbe дякую - але що робити, якщо я хочу, щоб ідентифікатор не був нульовим? Я хочу, щоб рейки автоматично присвоювали новий ідентифікатор, коли я створю дублікат - чи це можливо?
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 , які можуть бути застосовані як до моделі і на літа.

не забудьте перевірити документацію на Амебу, але використання досить просто ...

просто

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 має ще більше можливостей, тому обов'язково ознайомтеся з документацією.

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


Чудова відповідь. Дякую за деталі!
Дерек до

Спасибі це працює !! Але у мене є одне питання, як я можу додати нові записи з клонуванням перед збереженням клонованого об’єкта?
Mohd Anas

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 })). Це збереже нового користувача з правильним унікальним ідентифікатором.
RajeshM

Rails має Hash # за винятком і Hash # зріз , що потенційно робить запропонований метод найбільш потужним та менш схильним до помилок. Не потрібно додавати додаткові вкладки, їх легко розширити.
kucaahbe

10

Якщо вам потрібна глибока копія з асоціаціями, я рекомендую gem 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до своїх моделей, ви отримаєте доступ до таких методів:

nasledit_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

nasledit_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

Оскільки може бути більше логіки, при копіюванні моделі я б запропонував створити новий клас, де ви обробляєте всю необхідну логіку. Щоб полегшити це, є камінь, який може допомогти: клоун

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

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.