Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,20 @@ end
Multiple attribute names may be specified to define a compound key. Foreign key
column attributes (`user_id`) are often included in natural keys.

You can also use the :only_find option to disable creation or updating of unfound
objects entirely:

```ruby
class EmailAddress < ActiveRecord::Base
belongs_to :user
replicate_natural_key :user_id, :email, :only_find => true
end
```

In this example, loading a dump of users with email addresses will result in all
users loaded, but they will have only the email addresses that are already in the
target system.

### Omission of attributes and associations

You might want to exclude some attributes or associations from being dumped. For
Expand Down
21 changes: 19 additions & 2 deletions lib/replicate/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,25 @@ def replicate_associations=(names)
# Compound key used during load to locate existing objects for update.
# When no natural key is defined, objects are created new.
#
# Use :only_find => true as an option to only find existing objects - if
# none are found, they will not be created.
#
# attribute_names - Macro style setter.
def replicate_natural_key(*attribute_names)
options = attribute_names.last.is_a?(Hash) ? attribute_names.pop : {}
self.replicate_natural_key = attribute_names if attribute_names.any?
self.only_find_natural_key = options[:only_find] if options.has_key?(:only_find)
@replicate_natural_key || superclass.replicate_natural_key
end

def only_find_natural_key
@only_find_natural_key
end

def only_find_natural_key=(only_find)
@only_find_natural_key = only_find
end

# Set the compound key used to locate existing objects for update when
# loading. When not set, loading will always create new records.
#
Expand Down Expand Up @@ -175,6 +188,8 @@ def replicate_omit_attributes=(attribute_names)
# Load an individual record into the database. If the models defines a
# replicate_natural_key then an existing record will be updated if found
# instead of a new record being created.
# If the :only_find option is set to true on replicate_natural_key, will
# not create a new record if it isn't found.
#
# type - Model class name as a String.
# id - Primary key id of the record on the dump system. This must be
Expand All @@ -183,8 +198,9 @@ def replicate_omit_attributes=(attribute_names)
#
# Returns the ActiveRecord object instance for the new record.
def load_replicant(type, id, attributes)
instance = replicate_find_existing_record(attributes) || new
create_or_update_replicant instance, attributes
instance = replicate_find_existing_record(attributes)
return if instance.nil? and only_find_natural_key
create_or_update_replicant instance || new, attributes
end

# Locate an existing record using the replicate_natural_key attribute
Expand Down Expand Up @@ -308,6 +324,7 @@ def disable_query_cache!
::ActiveRecord::Base.replicate_associations = []
::ActiveRecord::Base.replicate_natural_key = []
::ActiveRecord::Base.replicate_omit_attributes = []
::ActiveRecord::Base.only_find_natural_key = false
::ActiveRecord::Base.replicate_id = false
end
end
56 changes: 56 additions & 0 deletions test/active_record_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,62 @@ def test_loading_everything
end
end

def test_only_find_natural_key_on_belongs_to
objects = []
@dumper.listen { |type, id, attrs, obj| objects << [type, id, attrs, obj] }

assert_equal(3, Profile.count)
assert_equal(3, User.count)

%w[rtomayko kneath tmm1].each do |login|
user = User.find_by_login(login)
@dumper.dump user
end
assert_equal 6, objects.size

User.find_by_login("kneath").profile.destroy
User.delete_all
assert_equal(0, User.count)
assert_equal(2, Profile.count)

# We only want to reattach profiles, not recreate them
Profile.replicate_natural_key :user_id, :only_find => true

# load everything back up
objects.each { |type, id, attrs, obj| @loader.feed type, id, attrs }

assert_equal(3, User.count)
assert_equal(2, Profile.count)
assert_nil User.find_by_login("kneath").profile
assert_not_nil User.find_by_login("rtomayko").profile
assert_not_nil User.find_by_login("tmm1").profile
end

def test_only_find_natural_key_on_has_many
objects = []
@dumper.listen { |type, id, attrs, obj| objects << [type, id, attrs, obj] }

User.replicate_associations :emails
Email.replicate_natural_key = []
Email.replicate_natural_key :email, :only_find => true

rtomayko = User.find_by_login('rtomayko')
@dumper.dump rtomayko
assert_equal 4, objects.size

emails = rtomayko.emails
Email.destroy_all

objects.each { |type, id, attrs, obj| @loader.feed type, id, attrs }
assert_equal(0, rtomayko.emails.count)

email = Email.create!(:email => emails.first.email)
assert_equal(0, rtomayko.emails.count)
objects.each { |type, id, attrs, obj| @loader.feed type, id, attrs }
assert_equal(1, rtomayko.emails.count)
assert_equal(email.email, rtomayko.reload.emails.first.email)
end

def test_loading_with_existing_records
objects = []
@dumper.listen { |type, id, attrs, obj| objects << [type, id, attrs, obj] }
Expand Down