Clonagem fácil de objetos active_record incluindo associações e diversas operações sob associações e atributos.
Veja aqui.
O objetivo era ser capaz de reproduzir objetos ActiveRecord de maneira fácil e rápida, incluindo seus filhos, por exemplo, copiando uma postagem de blog mantendo suas tags ou categorias associadas.
Esta joia é chamada de "Ameba" porque as amebas são (pequenas formas de vida) boas em se reproduzir. Seus filhos e netos também se reproduzem com rapidez e facilidade.
Uma gem de extensão ActiveRecord para permitir a duplicação de objetos de registro filho associados ao duplicar um modelo de registro ativo.
Compatível com trilhos 5.2, 6.0, 6.1. Para Rails 4.2 a 5.1 use a versão 3.x.
Suporta os seguintes tipos de associação
has_many
has_one :through
has_many :through
has_and_belongs_to_many
Uma DSL simples para configuração de quais campos copiar. O DSL pode ser aplicado aos seus modelos de trilhos ou usado dinamicamente.
Suporta filhos STI (Herança de Tabela Única) que herdam as configurações de ameba dos pais.
Vários estilos de configuração, como inclusivo, exclusivo e indiscriminado (também conhecido como copiar tudo).
Suporta a clonagem dos filhos de registros Muitos-para-Muitos ou apenas a manutenção de associações originais
Suporta detalhamento automático, ou seja, cópia recursiva de registros filho e neto.
Suporta pré-processamento de campos para ajudar a indicar a exclusividade e garantir a integridade dos seus dados, dependendo das necessidades da sua lógica de negócios, por exemplo, acrescentando "Cópia de" ou texto semelhante.
Suporta pré-processamento de campos com blocos lambda personalizados para que você possa fazer basicamente o que quiser se, por exemplo, precisar de alguma lógica personalizada ao fazer cópias.
Amoeba pode realizar as seguintes operações de pré-processamento em campos de registros copiados
definir
acrescentar
acrescentar
anular
personalizar
expressão regular
esperamos que seja como você esperaria:
gem instalar ameba
ou apenas adicione-o ao seu Gemfile:
gema 'ameba'
Configure seus modelos com um dos estilos abaixo e depois é só executar o método amoeba_dup
no seu modelo onde você executaria o método dup
normalmente:
p = Post.create(:title => "Olá mundo!", :content => "Lorum ipsum dolor")p.comments.create(:content => "Eu adorei!")p.comments.create(: content => "Isso é uma merda!")puts Comment.all.count # deve ser 2my_copy = p.amoeba_dupmy_copy.saveputs Comment.all.count # deve ser 4
Por padrão, quando ativado, o ameba copiará automaticamente todo e qualquer registro filho associado e os associará ao novo registro pai.
Você pode configurar o comportamento para incluir apenas os campos listados ou para incluir apenas os campos que você não exclui. Dos três, o estilo de maior desempenho será o estilo indiscriminado, seguido pelo estilo inclusivo, e o estilo exclusivo será o mais lento devido à necessidade de uma verificação extra explícita em cada campo. Essa diferença de desempenho é provavelmente insignificante o suficiente para que você possa escolher o estilo a ser usado com base no que é mais fácil de ler e escrever; no entanto, se sua árvore de dados for grande o suficiente e você precisar de controle sobre quais campos serão copiados, o estilo inclusivo provavelmente será melhor. escolha do que estilo exclusivo.
Observe que esses exemplos são apenas aproximações de cenários do mundo real e podem não ser particularmente realistas. Eles servem apenas para demonstrar o uso de recursos.
Este é o caso de uso mais básico e simplesmente permitirá a cópia de quaisquer associações conhecidas.
Se você tem alguns modelos para um blog assim:
classe Post <ActiveRecord::Base has_many :commentsendclass Comentário < ActiveRecord::Base pertence_a:postend
basta adicionar o bloco de configuração amoeba ao seu modelo e chamar o método enable para permitir a cópia de registros filhos, assim:
classe Post <ActiveRecord::Base has_many: comentários ameba factível endendclass Comentário < ActiveRecord::Base pertence_a:postend
Os registros filhos serão copiados automaticamente quando você executar o método amoeba_dup
.
Se você quiser copiar apenas algumas das associações, mas não outras, você pode usar o estilo inclusivo:
classe Post <ActiveRecord::Base has_many: comentários has_many :tags has_many: autores ameba doenableinclude_association: tagsinclude_association: autores endendclass Comentário < ActiveRecord::Base pertence_a:postend
Usar o estilo inclusivo dentro do bloco ameba na verdade implica que você deseja habilitar a ameba, portanto não há necessidade de executar o método enable, embora também não faça mal:
classe Post <ActiveRecord::Base has_many: comentários has_many :tags has_many: autores ameba doinclude_association: tagsinclude_association: autores endendclass Comentário < ActiveRecord::Base pertence_a:postend
Você também pode especificar os campos a serem copiados passando um array. Se você chamar include_association
com um único valor, ele será anexado à lista de campos já incluídos. Se você passar um array, seu array substituirá os valores originais.
classe Post <ActiveRecord::Base has_many: comentários has_many :tags has_many: autores ameba doinclude_association [:tags,:autores] endendclass Comentário < ActiveRecord::Base pertence_a:postend
Esses exemplos copiarão as tags e os autores da postagem, mas não seus comentários.
O estilo inclusivo, quando utilizado, desabilitará automaticamente qualquer outro estilo que tenha sido selecionado anteriormente.
Se você tiver mais campos para incluir do que para excluir, você pode querer reduzir a quantidade de digitação e leitura necessária usando o estilo exclusivo. Todos os campos que não forem explicitamente excluídos serão copiados:
classe Post <ActiveRecord::Base has_many: comentários has_many :tags has_many: autores ameba doexclude_association: comentários endendclass Comentário < ActiveRecord::Base pertence_a:postend
Este exemplo faz a mesma coisa que o exemplo de estilo inclusivo: copiará as tags e os autores da postagem, mas não seus comentários. Tal como acontece com o estilo inclusivo, não há necessidade de ativar explicitamente o ameba ao especificar campos a serem excluídos.
O estilo exclusivo, quando usado, desativará automaticamente qualquer outro estilo que tenha sido selecionado anteriormente, portanto, se você selecionou campos de inclusão e, em seguida, escolher alguns campos de exclusão, o método exclude_association
desativará o estilo inclusivo selecionado anteriormente e eliminará quaisquer campos de inclusão correspondentes. .
Além disso, se você precisar definir o caminho de uma condição extra para o relacionamento de inclusão ou exclusão, poderá definir o caminho do nome do método para a opção :if
.
classe Post <ActiveRecord::Base has_many: comentários has_many :tags ameba doinclude_association :comments, if: :popular? fim def popular?curtidas> 15 fim
Após a chamada Post.first.amoeba_dup
se likes
forem maiores que 15, todos os comentários também serão duplicados, mas em outra situação - nenhuma relação será clonada. O mesmo comportamento será para exclude_association
.
Esteja ciente ! Se você escreveu:
classe Post <ActiveRecord::Base has_many: comentários has_many :tags ameba doexclude_association :tagsinclude_association :comments, if: :popular? fim def popular?curtidas> 15 fim
estratégia de inclusão será escolhida independentemente do resultado da popular?
chamada de método (o mesmo para situação reversa).
Se você estiver usando um relacionamento muitos-para-muitos, poderá dizer ao ameba para realmente fazer duplicatas dos registros originais relacionados, em vez de apenas manter a associação com os registros originais. A clonagem é fácil, basta dizer ao ameba quais campos clonar, da mesma forma que você informa quais campos incluir ou excluir.
classe Post <ActiveRecord::Base has_and_belongs_to_many: avisos has_many:post_widgets has_many :widgets, :through => :post_widgets ameba doenableclone [:widgets, :warnings] endendclass Aviso <ActiveRecord::Base has_and_belongs_to_many :postsendclass PostWidget < ActiveRecord::Base pertence_a: widget pertence_to :postendclass Widget < ActiveRecord::Base has_many:post_widgets has_many :posts, :through => :post_widgetsend
Na verdade, este exemplo duplicará os avisos e widgets no banco de dados. Se originalmente havia 3 avisos no banco de dados, ao duplicar uma postagem, você terá 6 avisos no banco de dados. Isso contrasta com o comportamento padrão em que sua nova postagem seria meramente reassociada a quaisquer avisos existentes anteriormente e esses avisos em si não seriam duplicados.
Por padrão, o ameba reconhece e tenta copiar quaisquer filhos dos seguintes tipos de associação:
tem um
tem muitos
tem e pertence a muitos
Você pode controlar a quais tipos de associação o ameba se aplica usando o método recognize
dentro do bloco de configuração do ameba.
classe Post <ActiveRecord::Base has_one:config has_many: comentários has_and_belongs_to_many:tags ameba dorecognize [:has_one, :has_and_belongs_to_many] endendclass Comentário < ActiveRecord::Base pertence_to :postendclass Tag < ActiveRecord::Base has_and_belongs_to_many:postsend
Este exemplo copiará os dados de configuração da postagem e manterá as tags associadas à nova postagem, mas não copiará os comentários da postagem porque a ameba apenas reconhecerá e copiará os filhos das associações has_one
e has_and_belongs_to_many
e, neste exemplo, os comentários não são uma associação has_and_belongs_to_many
.
Se você deseja evitar que um campo regular (não baseado em associação has_*
) retenha seu valor quando copiado, você pode "zerar" ou "anular" o campo, assim:
classe Tópico < ActiveRecord::Base has_many :postsendclass Postagem < ActiveRecord::Base pertence_a: tópico has_many: comentários ameba doenablenullify:data_publicadonullify:topic_id endendclass Comentário < ActiveRecord::Base pertence_a:postend
Este exemplo copiará todos os comentários de uma postagem. Também anulará a data de publicação e dissociará a postagem de seu tema original.
Ao contrário dos estilos inclusivos e exclusivos, a especificação de campos nulos não permitirá automaticamente que o ameba copie todos os registros filhos. Como acontece com qualquer objeto de registro ativo, o valor padrão do campo será usado em vez de nil
se existir um valor padrão na migração.
Se você deseja apenas definir um campo com um valor arbitrário em todos os objetos duplicados, você pode usar a diretiva set
. Por exemplo, se você quiser copiar um objeto que tenha algum tipo de processo de aprovação associado a ele, provavelmente desejará definir o estado do novo objeto como aberto ou "em andamento" novamente.
classe Post <ActiveRecord::Base dose de ameba:state_tracker => "open_for_editing" fim
Neste exemplo, quando uma postagem é duplicada, seu campo state_tracker
sempre receberá um valor open_for_editing
para começar.
Você pode adicionar uma string ao início do campo de um objeto copiado durante a fase de cópia:
classe Post <ActiveRecord::Base amoeba doenableprepend :title => "Cópia de" fim
Você pode adicionar uma string ao final do campo de um objeto copiado durante a fase de cópia:
classe Post <ActiveRecord::Base amoeba doenableappend :title => "Cópia de" fim
Você pode executar uma consulta de pesquisa e substituição no campo de um objeto copiado durante a fase de cópia:
classe Post <ActiveRecord::Base amoeba doenableregex :contents => {:replace => /dog/, :with => 'cat'} fim
Você pode executar um método ou métodos customizados para fazer basicamente o que quiser, simplesmente passar um bloco lambda ou uma matriz de blocos lambda para a diretiva customize
. Cada bloco deve ter o mesmo formato, o que significa que cada bloco deve aceitar dois parâmetros, o objeto original e o objeto recém-copiado. Você pode então fazer o que quiser, assim:
classe Post <ActiveRecord::Base amoeba doprepend :title => "Olá mundo! "customize(lambda { |original_post,new_post| if original_post.foo == "bar"new_post.baz = "qux" end})append :comments => "... saiba o que Estou dizendo?" fim
ou isso, usando um array:
classe Post <ActiveRecord::Base has_and_belongs_to_many:tags amoeba doinclude_association :tagscustomize([ lambda do |orig_obj,copy_of_obj|# coisas boas vão aqui final, lambda do |orig_obj,copy_of_obj|# mais coisas boas vão aqui final]) fim
Os blocos Lambda passados para customização são executados, por padrão, após toda cópia e pré-processamento dos campos. Se você deseja executar um método antes de qualquer customização ou pré-processamento de campo, você pode usar override
o primo de customize
. O uso é o mesmo acima.
classe Post <ActiveRecord::Base amoeba doprepend :title => "Olá mundo! "override(lambda { |original_post,new_post| if original_post.foo == "bar"new_post.baz = "qux" end})append :comments => "... saiba o que Estou dizendo?" fim
Você pode aplicar um único pré-processador a vários campos de uma só vez.
classe Post <ActiveRecord::Base amoeba doenableprepend :title => "Cópia de ", :contents => "Conteúdo copiado: " fim
Você pode aplicar diversas diretivas de pré-processamento a um único modelo de uma só vez.
classe Post <ActiveRecord::Base amoeba doprepend :title => "Cópia de ", :contents => "Conteúdo original: "append :contents => " (versão copiada)"regex :contents => {:replace => /dog/, :with => ' gato'} fim
Este exemplo deve resultar em algo assim:
postar = Postar.create( :title => "Olá mundo", :contents => "Eu gosto de cachorros, cachorros são incríveis.")new_post = post.amoeba_dupnew_post.title # "Cópia de Olá mundo"new_post.contents # "Conteúdo original: Eu gosto de gatos, gatos são incríveis. (versão copiada)"
Assim como nullify
, as diretivas de pré-processamento não permitem automaticamente a cópia de registros filho associados. Se apenas diretivas de pré-processamento forem usadas e você desejar copiar registros filho e nenhuma lista include_association
ou exclude_association
for fornecida, você ainda deverá ativar explicitamente a cópia de registros filho chamando o método enable de dentro do bloco ameba em seu modelo.
Você pode usar uma combinação de métodos de configuração dentro do bloco de ameba de cada modelo. Os tipos de associação reconhecidos têm precedência sobre as listas de inclusão ou exclusão. O estilo inclusivo tem precedência sobre o estilo exclusivo, e estes dois estilos explícitos têm precedência sobre o estilo indiscriminado. Em outras palavras, se você listar os campos para copiar, o ameba copiará apenas os campos listados ou copiará apenas os campos que você não exclui, conforme o caso. Além disso, se um tipo de campo não for reconhecido ele não será copiado, independentemente de aparecer em uma lista de inclusão. Se você deseja que o ameba copie automaticamente todos os seus registros filhos, não liste nenhum campo usando include_association
ou exclude_association
.
O exemplo de sintaxe a seguir é perfeitamente válido e resultará no uso de estilo inclusivo. A ordem em que você chama os métodos de configuração no bloco ameba não importa:
classe Tópico < ActiveRecord::Base has_many :postsendclass Postagem < ActiveRecord::Base pertence_a: tópico has_many: comentários has_many :tags has_many: autores amoeba doexclude_association :authorsinclude_association :tagsnullify :date_publishedprepend :title => "Cópia de "append :contents => " (versão copiada)"regex :contents => {:replace => /dog/, :with => 'cat'}include_association :authorsenablenullify :topic_id endendclass Comentário < ActiveRecord::Base pertence_a:postend
Este exemplo copiará todas as tags e autores de uma postagem, mas não seus comentários. Também anulará a data de publicação e dissociará a postagem de seu tema original. Ele também irá pré-processar os campos da postagem como no exemplo de pré-processamento anterior.
Observe que, devido à precedência, o estilo inclusivo é usado e a lista de campos excluídos nunca é consultada. Além disso, o método enable
é redundante porque o ameba é ativado automaticamente ao usar include_association
.
As diretivas de pré-processamento são executadas após a cópia dos registros filho e são executadas nesta ordem.
Campos nulos
Precede
Acrescenta
Pesquisar e substituir
As diretivas de pré-processamento não afetam as listas de inclusão e exclusão.
Você pode fazer com que a ameba continue copiando a cadeia até onde quiser, simplesmente adicione blocos de ameba a cada modelo que deseja que copie seus filhos. Amoeba recorrerá automaticamente a quaisquer netos habilitados e os copiará também.
classe Post <ActiveRecord::Base has_many: comentários ameba factível endendclass Comentário < ActiveRecord::Base pertence_a:post has_many: avaliações ameba factível Classificação endendclass < ActiveRecord::Base pertence_a:comentar
Neste exemplo, quando uma postagem é copiada, o ameba copiará todos os comentários de uma postagem e também copiará as classificações de cada comentário.
Usar a associação has_one :through
é simples, apenas certifique-se de habilitar o ameba em cada modelo com uma associação has_one
e o ameba irá detalhar automática e recursivamente, assim:
classe Fornecedor < ActiveRecord::Base has_one: conta has_one :história, :através => :conta ameba factível endendclass Conta < ActiveRecord::Base pertence_a: fornecedor has_one: história ameba factível endendclass Histórico <ActiveRecord::Base pertence_a: conta
A cópia de has_many :through
associações funciona automaticamente. Eles executam a cópia da mesma maneira que a associação has_and_belongs_to_many
, o que significa que os registros filhos reais não são copiados, mas sim as associações são simplesmente mantidas. Você pode adicionar alguns pré-processadores de campo ao modelo intermediário, se desejar, mas isso não é estritamente necessário:
classe Assembly <ActiveRecord::Base has_many :manifestos has_many :partes, :through => :manifests ameba factível endendclass Manifesto <ActiveRecord::Base pertence_a: montagem pertence_a:parte ameba doprepend :notes => "Cópia de" endendclass Parte < ActiveRecord::Base has_many :manifestos has_many :assemblies, :through => :manifests ameba factível fim
Você pode controlar como o ameba copia seu objeto, dinamicamente, passando um bloco de configuração para o método ameba do modelo. O método de configuração é estático, mas a configuração é aplicada por instância.
classe Post <ActiveRecord::Base has_many: comentários amoeba doenableprepend :title => "Cópia de" endendclass Comentário < ActiveRecord::Base pertence_to :postendclass PostsController < ActionController def duplicado_a_postold_post = Post.create( :title => "Olá mundo", :contents => "Lorum ipsum")old_post.class.amoeba do prepend :contents => "Aqui está uma cópia: "endnew_post = old_post.amoeba_dupnew_post.title # deveria ser "Cópia de Olá mundo"new_post.contents # deveria ser "Aqui está uma cópia: Lorum ipsum"new_post.save fim
Se você estiver usando a herança de tabela única fornecida pelo ActiveRecord, poderá fazer com que o ameba processe automaticamente as classes filhas da mesma maneira que seus pais. Tudo que você precisa fazer é chamar o método propagate
dentro do bloco ameba da classe pai e todas as classes filhas devem copiar de maneira semelhante.
create_table :produtos, :force => true do |t| t.string:type # esta é a coluna STI # estes pertencem a todos os produtos t.string: título t.decimal: preço # estes são apenas para camisas t.decimal: comprimento_da manga t.decimal: tamanho_do_collar # estes são apenas para computadores t.inteiro:ram_size t.integer :hard_drive_sizeendclass Produto <ActiveRecord::Base has_many: imagens has_and_belongs_to_many:categorias ameba doenablepropagar endendclass Camisa <Produtoendclass Computador <Produtoendclass ProdutosControlador def some_methodmy_shirt = Shirt.find(1)my_shirt.amoeba_dupmy_shirt.save# esta camisa deve agora:# - ter sua própria cópia de todas as imagens pai# - estar nas mesmas categorias que o pai fim
Este exemplo deve duplicar todas as imagens e seções associadas a esta Camisa, que é filha de Produto
Por padrão, a propagação usa parentalidade submissa, o que significa que as configurações do pai serão aplicadas, mas quaisquer configurações filhas, se presentes, serão adicionadas ou sobrescritas às configurações pai, dependendo de como você chama os métodos DSL.
Você pode alterar esse comportamento, o chamado "estilo parental", para dar preferência às configurações dos pais ou ignorar toda e qualquer configuração dos filhos.
O estilo parental :relaxed
preferirá as configurações dos pais.
classe Produto < ActiveRecord::Base has_many: imagens has_and_belongs_to_many: seções ameba doexclude_association :imagespropagate :relaxado Camisa endendclass < Produto include_association: imagens include_association: seções prepend :title => "Cópia de "end
Neste exemplo, as configurações include_association
conflitantes no filho serão ignoradas e a configuração exclude_association
do pai será usada,