Я хочу сделать копию записи activerecord, изменив в процессе одно поле (в дополнение к я бы). Как это сделать проще всего?
Я понимаю, что могу создать новую запись, а затем перебирать каждое из полей, копируя данные поле за полем, но я подумал, что должен быть более простой способ сделать это ...
Такие как:
@newrecord=Record.copy(:id) *perhaps?*





Чтобы получить копию, используйте метод clone (или dup для rails 3.1+):
# rails < 3.1
new_record = old_record.clone
#rails >= 3.1
new_record = old_record.dup
Затем вы можете изменить любые поля, которые захотите.
ActiveRecord переопределяет встроенный объект Object # clone, чтобы предоставить вам новую (не сохраненную в БД) запись с неназначенным идентификатором. Обратите внимание, что он не копирует ассоциации, поэтому вам придется сделать это вручную, если вам нужно.
Клон Rails 3.1 - это мелкая копия, используйте вместо этого dup ...
Документы Rails 3.1 о клоне говорит, что он все еще работает, но я использую Rails 3.1.0.rc4, и даже метод new? не работает.
Когда я пытаюсь клонировать и затем сохранять в Rails 3.1 RC4, я получаю ошибку Mysql2::Error: Duplicate entry на первичном ключе.
Похоже, эта функция была заменена на dup: gist.github.com/994614
Однозначно НЕ используйте клон. Как упоминалось в других плакатах, метод clone теперь делегирует использование Kernel # clone, которое будет копировать идентификатор. С этого момента используйте ActiveRecord :: Base # dup
Делает ли dup то же самое, что и pre-rails 3.1? Или его функционал другой?
Должен сказать, это была настоящая боль. Подобное простое изменение предполагаемой функциональности могло бы парализовать некоторые важные функции, если бы у вас не было хорошего охвата спецификаций.
Дополнением для dup или clone, если вы хотите изменить определенные атрибуты, является использование tap, например. clone = record.dup.tap { |new_clone| new_clone.name = "dup_#{new_clone.name}" }
Мой тест показывает, что myrecord.clone дает мне точную копию записи, включая ее идентификатор. так что myrecord.save Я вернулся туда, откуда начал. myrecord.dup дает мне и точную копию, но идентификатор равен нулю. Теперь myrecord.save означает, что у меня есть дополнительная копия. Он скопировал все атрибуты, включая мои двоичные данные. Так что используйте ответ Луиса
После этого вы должны new_record.save! сохранить объект.
Обычно я просто копирую атрибуты, меняя все, что мне нужно изменить:
new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
Когда я это делаю, я получаю ошибку unknown attribute с одним столбцом из-за столбца, который существует из-за отношения has_many. Есть ли способ обойти это?
с rails4 он не создает уникальный идентификатор для записи
Чтобы создать новую запись с помощью Rails 4, выполните User.create(old_user.attributes.merge({ login: "newlogin", id: nil })). Это сохранит нового пользователя с правильным уникальным идентификатором.
В Rails есть Хэш # кроме и Хеш-сегмент, что потенциально делает предлагаемый метод наиболее мощным и менее подверженным ошибкам. Не нужно добавлять дополнительные библиотеки, их легко расширить.
В зависимости от ваших потребностей и стиля программирования вы также можете использовать комбинацию нового метода класса и слияния. Из-за отсутствия лучшего примера просто предположим, что у вас есть задача, запланированная на определенную дату, и вы хотите продублировать ее на другую дату. Фактические атрибуты задачи не важны, поэтому:
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.
Мир.
Не совсем уверен, насколько хороша идея, что вам вернули WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at
Когда я это делаю, я получаю ошибку неизвестного атрибута с одним столбцом из-за столбца, который существует из-за отношения has_many. Есть ли способ обойти это?
@RubenMartineJr. Я знаю, что это старый пост, но да, вы можете обойти это, используя '.except' в хэше атрибутов: new_task = Task.new (old_task.attributes.except (: attribute_you_dont_want,: another_aydw) .merge ({: schedule_on => some_new_date}))
@PhillipKoebbe, спасибо, но что, если я хочу, чтобы идентификатор не был нулевым? Я хочу, чтобы рельсы автоматически назначали новый идентификатор при создании дубликата - возможно ли это?
old task.attributes, к сожалению, также назначает поле идентификатора. У меня не работает
Используйте ActiveRecord :: Base # dup, если вы не хотите копировать идентификатор
@Thorin в соответствии с принятым ответом выше, похоже, что правильный метод для Rails <3.1 - .clone
Если вам нужна глубокая копия с ассоциациями, я рекомендую гем deep_cloneable.
Я тоже. Я попробовал этот драгоценный камень, и он сработал с первого раза, очень прост в использовании.
Вам также может понравиться Драгоценный камень амебы для 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 имеет еще больше опций, поэтому обязательно ознакомьтесь с документацией.
Наслаждаться! :)
Отличный ответ. Спасибо за подробности!
Спасибо, это работает!! Но у меня есть один вопрос, как мне добавить новые записи с клонированием перед сохранением клонированного объекта?
Просто исправление здесь. Правильный метод - .amoeba_dup, а не только .dup. Я пытался выполнить этот код, но здесь он не работал.
Вы также можете проверить гем act_as_inheritable.
«Acts As Inheritable - это драгоценный камень Ruby, специально написанный для моделей 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">
Надеюсь, это поможет тебе.
Самый простой способ:
#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
Поскольку при дублировании модели может быть больше логики, я бы предложил создать новый класс, в котором вы будете обрабатывать всю необходимую логику. Чтобы облегчить это, есть драгоценный камень, который может помочь: клоун
Согласно их примерам документации, для модели 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: '[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
Пример скопирован из проекта, но он даст четкое представление о том, чего вы можете достичь.
Для быстрой и простой записи я бы выбрал:
Model.new(Model.last.attributes.reject {|k,_v| k.to_s == 'id'}
Вот пример переопределения метода 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.
В Rails 5 вы можете просто создать дубликат объекта или такой записи.
new_user = old_user.dup
Это все еще работает в Rails 3.1.0.beta? Когда я использую
q = p.clone, а затемp == q, я получаю обратноtrue. С другой стороны, если я используюq = p.dup, я получаю обратноfalseпри их сравнении.