Module: Copyable::CopyableExtension::ClassMethods
- Defined in:
- lib/copyable/copyable_extension.rb
Instance Method Summary collapse
-
#copyable(&block) ⇒ Object
Use this copyable declaration in an ActiveRecord model to instruct the model how to copy itself.
Instance Method Details
#copyable(&block) ⇒ Object
Use this copyable declaration in an ActiveRecord model to instruct the model how to copy itself. This declaration will create a create_copy! method that follows the instructions in the copyable declaration.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/copyable/copyable_extension.rb', line 14 def copyable(&block) begin model_class = self # raise an error if the copyable declaration is stated incorrectly SyntaxChecker.check!(model_class, block) # "execute" the copyable declaration, which basically saves the # information listed in the declaration for later use by the # create_copy! method main = Declarations::Main.new main.execute(block) rescue => e # if suppressing schema errors, don't raise an error, but also don't define a create_copy! method since it would have broken behavior return if (e.is_a?(Copyable::ColumnError) || e.is_a?(Copyable::AssociationError) || e.is_a?(ActiveRecord::StatementInvalid)) && Copyable.config.suppress_schema_errors == true raise end # define a create_copy! method for use on model objects define_method(:create_copy!) do |={}| # raise an error if passed invalid options OptionChecker.check!() # we basically wrap the method in a lambda to help us manage # running it in a transaction do_the_copy = lambda do || new_model = nil begin # start by disabling all callbacks and observers (except for # validation callbacks and observers) ModelHooks.disable!(model_class) # rename self for clarity original_model = self # create a brand new, empty model new_model = model_class.new # fill in each column of this brand new model according to the # instructions given in the copyable declaration column_overrides = [:override] || {} # merge with global override hash if exists column_overrides = column_overrides.merge([:global_override]) if [:global_override] Declarations::Columns.execute(main.column_list, original_model, new_model, column_overrides) # save that sucker! Copyable::Saver.save!(new_model, [:skip_validations]) # tell the registry that we've created a new model (the registry # helps keep us from creating another copy of a model we've # already copied) CopyRegistry.register(original_model, new_model) # for this brand new model, visit all of the associated models, # making new copies according to the instructions in the copyable # declaration skip_associations = [:skip_associations] || [] Declarations::Associations.execute(main.association_list, original_model, new_model, [:global_override], [:skip_validations], skip_associations) # run the after_copy block if it exists Declarations::AfterCopy.execute(main.after_copy_block, original_model, new_model) ensure # it's critically important to re-enable the callbacks and # observers or they will stay disabled for future web # requests ModelHooks.reenable!(model_class) end # it's polite to return the newly created model new_model end # create_copy! can end up calling itself (by copying associations). # There is some behavior that we want to be slightly different if # create_copy! is called from within another create_copy! call. # (This means that any create_copy! call in copyable's internal # code should pass { __called_recursively: true } to create_copy!. if [:__called_recursively] do_the_copy.call() else # Imagine the case where you have a model hierarchy such as # a Book that has many Sections that has many Pages. # # When @book.create_copy! is called, the CopyRegistry will keep # track of all of the copied models, making sure no model is # re-duplicated (such as in an unusual case where book sections # actually overlapped, and therefore two different sections # contained some of the same pages--you wouldn't want to re-copy # the pages). # # If we don't clear the registry before we start @book.create_copy!, # then we can't do this: # # copy1 = @book.create_copy! # copy2 = @book.create_copy! # # since when copying copy2, the CopyRegistry will remember the # Sections and Pages that it copied for copy1 and therefore # they won't get recopied. So we have to clear the CopyRegistry's # memory each time before create_copy! is called. CopyRegistry.clear # Nested transactions can end up swallowing ActiveRecord::Rollback # errors in surprising ways. create_copy! can eventually call # create_copy! when copying associated objects, which can result # in nested transactions. We use this option to avoid the nesting. ActiveRecord::Base.transaction do do_the_copy.call() end end end end |