Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Package as gem #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
*.iml
Gemfile.lock
5 changes: 5 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
source 'https://rubygems.org'

gem 'rspec', '~> 3.4.0'
gem 'activerecord', '~> 4.2.0'
gem 'mysql2', '~> 0.4.3'
3 changes: 0 additions & 3 deletions README

This file was deleted.

16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# activerecord_merge
## Simple merge for ActiveRecord objects and their associations

### Installation
```
gem install activerecord_merge
```

### Usage in Rails
Create `config/initializers/activerecord_merge.rb` with the following line of code
```
require 'merge'
```

### More info
See [http://ewout.name/2010/04/generic-deep-merge-for-activerecord](http://ewout.name/2010/04/generic-deep-merge-for-activerecord)
12 changes: 12 additions & 0 deletions activerecord_merge.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Gem::Specification.new do |s|
s.name = 'activerecord_merge'
s.version = '1.0.0'
s.date = '2016-03-07'
s.summary = 'ActiveRecord object merge'
s.description = 'Simple merge for ActiveRecord objects and their associations.'
s.authors = ['Ewout Van Troostenberghe']
s.files = ['lib/merge.rb']
s.homepage =
'http://rubygems.org/gems/activerecord_merge'
s.license = 'MIT'
end
56 changes: 28 additions & 28 deletions lib/merge.rb
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
module Merge

# True if self is safe to merge with +object+, ie they are more or less equal.
# Default implementation compares all attributes except id and metadata.
# Can be overridden in specific models that have a neater way of comparison.
def merge_equal?(object)
object.instance_of?(self.class) and merge_attributes == object.merge_attributes
object.instance_of?(self.class) && merge_attributes == object.merge_attributes
end

MERGE_INDIFFERENT_ATTRIBUTES = %w(id position created_at updated_at creator_id updater_id).freeze
MERGE_EXCLUDE_ASSOCIATIONS = [].freeze

# Attribute hash used for comparison.
def merge_attributes
merge_attribute_names.inject({}) do |attrs, name|
attrs[name] = self[name]
attrs
end
end

# Names of the attributes that should be merged.
def merge_attribute_names
attribute_names - MERGE_INDIFFERENT_ATTRIBUTES
end
# Names of associations excluded from the merge.

# Names of associations excluded from the merge.
# Override if the model has multiple scoped associations,
# that can all be retrieved by a single has_many association.
def merge_exclude_associations
MERGE_EXCLUDE_ASSOCIATIONS
end
# Merge this object with the given +objects+.

# Merge this object with the given +objects+.
# This object will serve as the master,
# blank attributes will be taken from the given objects, in order.
# All associations to +objects+ will be assigned to +self+.
Expand All @@ -42,59 +41,60 @@ def merge!(*objects)
objects.each do |object|
if r.macro == :has_one
other = object.send(r.name)
if local and other
if local && other
local.merge!(other)
elsif other
send("#{r.name}=", other)
end
# TODO: remove this condition to test has_and_belongs_to_many relations. This condition was only added to raise error
elsif r.macro == :has_and_belongs_to_many
p 'activerecord_merge: merging objects with has_and_belongs_to_many is currently broken'
else
other = object.send(r.name) - local
# May be better to compare without the primary key attribute instead of setting it.
other.each {|o| o[r.foreign_key] = self.id}
other.reject! {|o| local.any? {|l| merge_if_equal(l,o) }}
other.each { |o| o[r.foreign_key] = id }
other.reject! { |o| local.any? { |l| merge_if_equal(l, o) } }
local << other
end
end
end
objects.each {|o| o.reload and o.destroy unless o.new_record?}
objects.each { |o| o.reload && o.destroy unless o.new_record? }
end
end

def merge_attributes!(*objects)
blank_attributes = merge_attribute_names.select {|att| self[att].blank?}
until blank_attributes.empty? or objects.empty?
blank_attributes = merge_attribute_names.select { |att| self[att].blank? }
until blank_attributes.empty? || objects.empty?
object = objects.shift
blank_attributes.reject! do |att|
if val = object[att] and not val.blank?
if val = object[att] and !val.blank?
self[att] = val
end
end
end
save!
end

private

def merge_association_reflections
self.class.reflect_on_all_associations.select do |r|
[:has_many, :has_one, :has_and_belongs_to_many].include?(r.macro) and
not r.options[:through] and
not merge_exclude_associations.include?(r.name.to_sym)
self.class.reflect_on_all_associations.select do |r|
[:has_many, :has_one, :has_and_belongs_to_many].include?(r.macro) &&
!r.options[:through] &&
!merge_exclude_associations.include?(r.name.to_sym)
end
end

def merge_if_equal(master, object)
if master.merge_equal?(object)
master.merge!(object) ; true
master.merge!(object); true
end
end

end

ActiveRecord::Base.class_eval { include Merge }

# Compatibility with ActiveRecord 2.x
class ActiveRecord::Reflection::AssociationReflection
alias :foreign_key :primary_key_name unless method_defined? :foreign_key
alias_method :foreign_key, :primary_key_name unless method_defined? :foreign_key
end

9 changes: 5 additions & 4 deletions spec/database.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
adapter: mysql
adapter: mysql2
encoding: utf8
database: activerecord_merge_test
username: root
password:
socket: /opt/local/var/run/mysql5/mysqld.sock
encoding: utf8
password:
host: localhost
port: 3306
Loading