Jaki jest najłatwiejszy sposób na skopiowanie rekordu activerecord?

412

Chcę zrobić kopię rekordu activerecord, zmieniając pojedyncze pole w tym procesie (oprócz id ). Jaki jest najprostszy sposób na osiągnięcie tego?

Zdaję sobie sprawę, że mogę utworzyć nowy rekord, a następnie wykonać iterację po każdym polu kopiującym dane pole po polu - ale pomyślałem, że musi istnieć łatwiejszy sposób na zrobienie tego ...

Jak na przykład:

 @newrecord=Record.copy(:id)  *perhaps?*
Brent
źródło

Odpowiedzi:

622

Aby uzyskać kopię, użyj metody klonowania (lub duplikowania w przypadku szyn 3.1+):

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Następnie możesz zmienić dowolne pola.

ActiveRecord przesłania wbudowany klon Object #, aby dać ci nowy (nie zapisany w DB) rekord z nieprzypisanym identyfikatorem.
Pamiętaj, że nie kopiuje skojarzeń, więc musisz to zrobić ręcznie, jeśli zajdzie taka potrzeba.

Klon Rails 3.1 jest płytką kopią, zamiast tego użyj dup ...

Michael Sepcot
źródło
6
Czy to nadal działa w Rails 3.1.0.beta? Kiedy robię q = p.clone, a potem p == q, mam truepowrotem. Z drugiej strony, jeśli użyję q = p.dup, falsewrócę podczas ich porównywania.
Autumnsault
1
Dokumenty Rails 3.1 na klonie mówią, że nadal działa, ale używam Rails 3.1.0.rc4 i nawet ta new?metoda nie działa.
Turadg
12
Wygląda na to, że ta funkcjonalność została zastąpiona przez dup: gist.github.com/994614
skattyadz
74
Zdecydowanie NIE używaj klonowania. Jak wspomnieli inni plakaty, metoda klonowania deleguje teraz do używania klonowania jądra #, które skopiuje identyfikator. Używaj ActiveRecord :: Base # dup od teraz
bradgonesurfing
5
Muszę powiedzieć, że to był prawdziwy ból. Taka prosta zmiana zamierzonej funkcjonalności może zniszczyć niektóre ważne funkcje, jeśli nie masz dobrego zasięgu specyfikacji.
Matt Smith
74

W zależności od potrzeb i stylu programowania możesz także użyć kombinacji nowej metody klasy i scalenia. Z braku lepszego prostego przykładu załóżmy, że masz zaplanowane zadanie na określoną datę i chcesz je zduplikować na inną datę. Rzeczywiste atrybuty zadania nie są ważne, więc:

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

utworzy nowe zadanie z :id => nil, :scheduled_on => some_new_datei wszystkie inne atrybuty będą takie same jak pierwotne zadanie. Korzystając z Task.new, będziesz musiał jawnie wywołać save, więc jeśli chcesz, aby zapisywał się automatycznie, zmień Task.new na Task.create.

Pokój.

Phillip Koebbe
źródło
5
Nie jestem pewien, czy to dobry pomysł, b / c zostaniesz WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_atzwrócony
bcackerman,
Gdy to robię, pojawia się nieznany błąd atrybutu w jednej kolumnie z powodu kolumny, która istnieje z powodu relacji has_many. Czy jest na to jakiś sposób?
Ruben Martinez Jr.
2
@RubenMartineJr. Wiem, że to jest stary post, ale tak, można to obejść, używając „.except” na skrócie atrybutów: new_task = Task.new (old_task.attributes.except (: attribute_you_dont_want,: another_aydw) .merge ({: schedule_on => some_new_date}))
Ninigi
@PhillipKoebbe dziękuję - ale co, jeśli chcę, aby identyfikator nie był zerowy? Chcę, aby szyny automatycznie przypisywały nowy identyfikator podczas tworzenia duplikatu - czy jest to możliwe?
BKSpurgeon
1
old_task.attribtes przypisuje również pole ID. Dla mnie to nie działa
BKSpurgeon
32

Może ci się spodobać klejnot Amoeba dla ActiveRecord 3.2.

W twoim przypadku, prawdopodobnie chcesz skorzystać z nullify, regexlub prefixopcji dostępnych w konfiguracji DSL.

To pozwala na łatwe i automatyczne rekurencyjną powielania has_one, has_manyoraz has_and_belongs_to_manystowarzyszenia, wstępne przetwarzanie pole i wysoce elastyczny i potężny DSL konfiguracji, które mogą być stosowane zarówno do modelu i na bieżąco.

koniecznie sprawdź dokumentację Amoeba, ale korzystanie z niej jest dość łatwe ...

właśnie

gem install amoeba

lub dodaj

gem 'amoeba'

do twojego Gemfile

następnie dodaj blok ameby do swojego modelu i uruchom dupmetodę jak zwykle

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

Możesz także kontrolować, które pola są kopiowane na wiele sposobów, ale na przykład, jeśli chcesz zapobiec duplikowaniu komentarzy, ale chcesz zachować te same tagi, możesz zrobić coś takiego:

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

  amoeba do
    exclude_field :comments
  end
end

Możesz również wstępnie przetwarzać pola, aby pomóc wskazać unikalność zarówno przedrostków, jak i przyrostków, a także wyrażeń regularnych. Ponadto istnieje wiele opcji, dzięki którym możesz pisać w najbardziej czytelnym stylu:

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

Rekurencyjne kopiowanie skojarzeń jest łatwe, po prostu włącz amebę także w modelach potomnych

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

Konfiguracja DSL ma jeszcze więcej opcji, więc sprawdź dokumentację.

Cieszyć się! :)

Vaughn Draughon
źródło
Świetna odpowiedź. Dzięki za szczegóły!
Derek Prior
Dzięki, działa !! Ale mam jedno pytanie, jak dodać nowe wpisy z klonowaniem przed zapisaniem sklonowanego obiektu?
Mohd Anas,
1
Tylko poprawka tutaj. Prawidłowa metoda to .amoeba_dupnie tylko .dup. Próbowałem wykonać ten kod, ale tutaj nie działał.
Victor
31

Użyj ActiveRecord :: Base # dup, jeśli nie chcesz kopiować identyfikatora

bradgonesurfing
źródło
1
@Thorin jak na odpowiedź przyjętą powyżej, wygląda prawidłowy sposób Rails <3.1.clone
Dan Tkacz
24

Zwykle po prostu kopiuję atrybuty, zmieniając wszystko, co muszę zmienić:

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
François Beausoleil
źródło
Gdy to robię, unknown attributepojawia się błąd w jednej kolumnie z powodu kolumny, która jest tam z powodu relacji has_many. Czy jest na to jakiś sposób?
Ruben Martinez Jr.
z rails4 nie tworzy unikalnego identyfikatora dla rekordu
Ben
4
Aby utworzyć nowy rekord za pomocą Rails 4, wykonaj User.create(old_user.attributes.merge({ login: "newlogin", id: nil })). Spowoduje to zapisanie nowego użytkownika z poprawnym unikalnym identyfikatorem.
RajeshM,
Railsy mają Hash # oprócz i Hash # slice , potencjalnie czyniąc sugerowaną metodę najmocniejszą i mniej podatną na błędy. Nie ma potrzeby dodawania dodatkowych bibliotek, łatwe do rozszerzenia.
kucaahbe
10

Jeśli potrzebujesz głębokiej kopii ze skojarzeniami, polecam klejnot deep_cloneable .

raidfive
źródło
Ja też. Wypróbowałem ten klejnot i zadziałał za pierwszym razem, bardzo łatwy w użyciu.
Rob
4

W Rails 5 możesz po prostu utworzyć duplikat obiektu lub nagrać w ten sposób.

new_user = old_user.dup
Foram Thakral
źródło
2

Łatwy sposób to:

#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

Lub

# if your rails < 3.1
 o = Model.find(id)
 (1..109).each do |item|
   new_record = o.clone
   new_record.save
 end     
ThienSuBS
źródło
2

Oto próbka nadpisującej #dupmetody ActiveRecord w celu dostosowania duplikacji instancji i uwzględnienia również duplikacji relacji:

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

Uwaga: ta metoda nie wymaga zewnętrznego klejnotu, ale wymaga nowszej wersji ActiveRecord z #dupzaimplementowaną metodą

Zoran Majstorovic
źródło
0

Możesz także sprawdzić klejnot act_as_inheritable .

„Działa jako dziedziczny to rubinowy klejnot napisany specjalnie dla modeli Rails / ActiveRecord. Jest przeznaczony do użycia z powiązaniem samo-referencyjnym lub z modelem posiadającym rodzica, który ma wspólne atrybuty dziedziczne. Pozwoli ci to odziedziczyć dowolny atrybut lub relacja z modelu nadrzędnego ”.

Dodając acts_as_inheritabledo swoich modeli będziesz miał dostęp do tych metod:

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

Mam nadzieję, że to może ci pomóc.

esbanarango
źródło
0

Ponieważ może istnieć więcej logiki, przy powielaniu modelu sugerowałbym utworzenie nowej klasy, w której obsłużysz całą potrzebną logikę. Aby to ułatwić, istnieje klejnot, który może pomóc: clowne

Zgodnie z przykładami dokumentacji dla modelu użytkownika:

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

Tworzysz klasę klonera:

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

a następnie użyj go:

user = User.last
#=> <#User(login: 'clown', email: '[email protected]')>

cloned = UserCloner.call(user, email: '[email protected]')
cloned.persisted?
# => false

cloned.save!
cloned.login
# => nil
cloned.email
# => "[email protected]"

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

Przykład skopiowany z projektu, ale da jasną wizję tego, co możesz osiągnąć.

Dla szybkiego i prostego zapisu wybrałbym:

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

Paulo Fidalgo
źródło