I want to merge two profiles into one. What is the best way to do this in Rails.
I have two profiles say user1 and user2 and there are at least 30 tables associated with them.
Now i want to merge them together so that there should be one profile say user1 and user2 should get deleted but all the associated data of user2 now should associate with user1.
For example: suppose user2 has two contacts and user1 has 3 contacts after merging user user1 should have 5 contacts.
Something like this
#user1 = User.find(1);
#user2 = User.find(2);
Contact.where("user_id = ?", #user2.id).update_all(:user_id => #user1.id)
#user2.destroy
in case of generalize solution
place file /lib/acts_as_user_merge.rb
module UserMerge
module ActsAsUserMerge
module Base
def self.included(klass)
klass.class_eval do
extend Config
end
end
end
module Config
def acts_as_user_merge
include ::UserMerge::ActsAsUserMerge::InstanceMethods
end
end
module InstanceMethods
def merge(user)
models = Array.new
models_names = User.reflections.collect{|a, b| b.class_name if b.macro==:has_many}.compact
models_names.each do |name|
models << Object.const_get name
end
models.each do |model|
model.where("user_id = ?", user.id).update_all(:user_id => self.id)
end
user.destroy
end
end
end
end
::ActiveRecord::Base.send :include, ::UserMerge::ActsAsUserMerge::Base
how to use
User < ActiveRecord::Base
has_many ...
acts_as_user_merge
end
#user1.merge(#user2)
kinda messy and not tested but should give you an idea
Something like that
def merge_users(dead, user)
User.reflections.each do |assoc, reflection|
foreign_key = reflection.foreign_key
case reflection.macro
when :has_many, :has_one then
unless reflection.options[:through]
reflection.klass.where(foreign_key => dead.id).update_all(foreign_key => user.id) # if id is a primary key
end
if options[:as] # polymorphic
if reflection.macro == :has_many
dead.send("#{options[:as].pluralize}")).each { |r| user.send("#{options[:as].pluralize}<<", r) }
else
user.send("#{options[:as]}=", dead.send("#{options[:as]}"))
user.save
end
end
when :belongs_to then
if options[:polymorphic]
user.send("#{assoc}=", deaf.send(assoc))
user.save
else
user.update_attribute(foreign_key, dead.send(foreign_key))
end
when :has_and_belongs_to_many then
dead.send("#{assoc}")).each { |r| user.send("#{assoc}<<", r) }
end
end
end
merge_users(dead_user, user)
dead_user.destroy
This article discusses this matter in depth, and provides the working code for it: http://ewout.name/2010/04/generic-deep-merge-for-activerecord/
Related
Django admin shows you the dependent records that will be deleted when you delete a record as a confirmation.
Is there a way to do the same on Ruby on Rails?
I have been researching how to do it, but I am still looking for a way.
I couldn't find a gem, so I wrote this concern using association reflections:
module DependentDestroys
extend ActiveSupport::Concern
DEPENDENT_DESTROY_ACTIONS = %i[destroy delete destroy_async]
class_methods do
def dependent_destroy_reflections
#dependent_destroy_reflections ||= reflections.filter_map do |name, r|
r if DEPENDENT_DESTROY_ACTIONS.include?(r.options[:dependent])
end
end
end
def total_dependent_destroys
dependent_destroy_counts.sum { |r| r[1] }
end
def any_dependent_destroys?
dependent_destroy_counts.any?
end
# If you want all affected records...
def dependent_destroy_records
self.class.dependent_destroy_reflections.flat_map do |r|
relation = self.public_send(r.name)
if r.collection?
relation.find_each.to_a
else
relation
end
end
end
# If you only want the record type and ids...
def dependent_destroy_ids
self.class.dependent_destroy_reflections.flat_map do |r|
relation = self.public_send(r.name)
if r.collection?
relation.pluck(:id).map { |rid| [r.klass, rid] }
else
[[r.klass, relation.id]] if relation
end
end.compact
end
# If you only want counts...
def dependent_destroy_counts
self.class.dependent_destroy_reflections.filter_map do |r|
relation = self.public_send(r.name)
if r.collection?
c = relation.count
[r.klass, c] if c.positive?
else
[r.klass, 1] if relation
end
end
end
def dependent_destroy_total_message
"#{total_dependent_destroys} associated records will be destroyed"
end
def dependent_destroy_message
# Using #human means you can define model names in your translations.
"The following dependent records will be destroyed: #{dependent_destroy_ids.map { |r| "#{r[0].model_name.human}/#{r[1]}" }.join(', ')}"
end
def dependent_destroy_count_message
"The following dependent records will be destroyed: #{dependent_destroy_counts.map { |r| "#{r[0].model_name.human(count: r[1])} (#{r[1]})" }.join(', ')}"
end
end
Usage:
class User
include DependentDestroys
belongs_to :company
has_many :notes
has_one :profile
end
user = User.first
user.any_dependent_destroys?
# => true
user.total_dependent_destroys
# => 60
user.dependent_destroy_total_message
# => "60 associated records will be destroyed"
user.dependent_destroy_message
# => "The following dependent records will be destroyed: Note/1, Note/2, ..., Profile/1"
user.dependent_destroy_count_message
# => "The following dependent records will be destroyed: Notes (59), Profile (1)"
You can then use these methods in the controller to deal with the user flow.
With some improvements, options (like limiting it to the associations or modes (destroy, delete, destroy_async) you want) and tests, this could become a gem.
Given 2 resources:
jsonapi_resources :companies
jsonapi_resources :users
User has_many Companies
default_paginator = :paged
/companies request is paginated and that's what I want. But I also want to disable it for relationship request /users/4/companies. How to do this?
The best solution I found will be to override JSONAPI::RequestParser#parse_pagination like this:
class CustomNonePaginator < JSONAPI::Paginator
def initialize
end
def apply(relation, _order_options)
relation
end
def calculate_page_count(record_count)
record_count
end
end
class JSONAPI::RequestParser
def parse_pagination(page)
if disable_pagination?
#paginator = CustomNonePaginator.new
else
original_parse_pagination(page)
end
end
def disable_pagination?
# your logic here
# request params are available through #params or #context variables
# so you get your action, path or any context data
end
def original_parse_pagination(page)
paginator_name = #resource_klass._paginator
#paginator = JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
rescue JSONAPI::Exceptions::Error => e
#errors.concat(e.errors)
end
end
I have an activity feed that currently show all activity including current user. My goal is to only update the feed with friends' activity and not the current user's activity. I believe it has something to do with the map(&:id) method, but I'm not positive. Any guidance would be super awesome!
activity.rb:
class Activity < ActiveRecord::Base
belongs_to :user
belongs_to :targetable, polymorphic: true
self.per_page = 10
def self.for_user(user, options={})
options[:page] ||= 1
# return WillPaginate::Collection.new(1, per_page, 1) unless user
friend_ids = user.friends.map(&:id).push(user.id)
collection = where("user_id in (?)", friend_ids).order("created_at desc")
if options[:since] && !options[:since].blank?
since = DateTime.strptime( options[:since], '%s' )
collection = collection.where("created_at > ?", since) if since
end
collection.page(options[:page])
end
def user_name
user.name
end
def username
user.username
end
def as_json(options={})
super(
only: [:action, :id, :targetable_id, :targetable_type, :created_at, :id],
include: :targetable,
methods: [:user_name, :username]
).merge(options)
end
end
The .push(user.id) is adding the user's own ID onto this list of friend IDs so if you just remove that you'll get the activities for just the friends.
friends.map(&:id) takes the list of the user's friends and maps this to a list of the IDs for those friends ready for use in the where condition on the next line i.e. it calls id on each friend and returns an array of the results of that.
I have a simple model with an after_create filter that creates association records.
class Subject
after_create :create_topics!
has_paper_trail :on => [:create, :update],
:ignore => [:topics]
private
def create_topics!
self.account.default_topics_for_subject_type(self.subject_type).each do |topic|
self.topics.create!({:name => topic.name})
end
end
end
However, creating a Subject now which e.g will create two topics results in two versions for the same subject, a create before and an update after the topics have changed.
Any ideas on how to solve this?
update
The topic model is not a subclass from subject, but belongs to it. They also have a paper_trail and should be versioned right from the beginning of the creation process through subject.
class Topic
belongs_to :subject
end
private
def create_topics!
account.default_topics_for_subject_type(subject_type).each_with_index do |topic, index|
if index == 0
create_topic!(topic)
else
without_versioning { create_topic!(topic) }
end
end
end
def create_topic!(topic)
self.topics.create!({:name => topic.name})
end
I have an (I think) relatively straightforward has_many :through relationship with a join table:
class User < ActiveRecord::Base
has_many :user_following_thing_relationships
has_many :things, :through => :user_following_thing_relationships
end
class Thing < ActiveRecord::Base
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
end
class UserFollowingThingRelationship < ActiveRecord::Base
belongs_to :thing
belongs_to :user
end
And these rspec tests (I know these are not necessarily good tests, these are just to illustrate what's happening):
describe Thing do
before(:each) do
#user = User.create!(:name => "Fred")
#thing = Thing.create!(:name => "Foo")
#user.things << #thing
end
it "should have created a relationship" do
UserFollowingThingRelationship.first.user.should == #user
UserFollowingThingRelationship.first.thing.should == #thing
end
it "should have followers" do
#thing.followers.should == [#user]
end
end
This works fine UNTIL I add an after_save to the Thing model that references its followers. That is, if I do
class Thing < ActiveRecord::Base
after_save :do_stuff
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
def do_stuff
followers.each { |f| puts "I'm followed by #{f.name}" }
end
end
Then the second test fails - i.e., the relationship is still added to the join table, but #thing.followers returns an empty array. Furthermore, that part of the callback never gets called (as if followers is empty within the model). If I add a puts "HI" in the callback before the followers.each line, the "HI" shows up on stdout, so I know the callback is being called. If I comment out the followers.each line, then the tests pass again.
If I do this all through the console, it works fine. I.e., I can do
>> t = Thing.create!(:name => "Foo")
>> t.followers # []
>> u = User.create!(:name => "Bar")
>> u.things << t
>> t.followers # [u]
>> t.save # just to be super duper sure that the callback is triggered
>> t.followers # still [u]
Why is this failing in rspec? Am I doing something horribly wrong?
Update
Everything works if I manually define Thing#followers as
def followers
user_following_thing_relationships.all.map{ |r| r.user }
end
This leads me to believe that perhaps I am defining my has_many :through with :source incorrectly?
Update
I've created a minimal example project and put it on github: https://github.com/dantswain/RspecHasMany
Another Update
Thanks a ton to #PeterNixey and #kikuchiyo for their suggestions below. The final answer turned out to be a combination of both answers and I wish I could split credit between them. I've updated the github project with what I think is the cleanest solution and pushed the changes: https://github.com/dantswain/RspecHasMany
I would still love it if someone could give me a really solid explanation of what is going on here. The most troubling bit for me is why, in the initial problem statement, everything (except the operation of the callback itself) would work if I commented out the reference to followers.
I've had similar problems in the past that have been resolved by reloading the association (rather than the parent object).
Does it work if you reload thing.followers in the RSpec?
it "should have followers" do
#thing.followers.reload
#thing.followers.should == [#user]
end
EDIT
If (as you mention) you're having problems with the callbacks not getting fired then you could do this reloading in the object itself:
class Thing < ActiveRecord::Base
after_save { followers.reload}
after_save :do_stuff
...
end
or
class Thing < ActiveRecord::Base
...
def do_stuff
followers.reload
...
end
end
I don't know why RSpec has issues with not reloading associations but I've hit the same types of problems myself
Edit 2
Although #dantswain confirmed that the followers.reload helped alleviate some of the problems it still didn't fix all of them.
To do that, the solution needed a fix from #kikuchiyo which required calling save after doing the callbacks in Thing:
describe Thing do
before :each do
...
#user.things << #thing
#thing.run_callbacks(:save)
end
...
end
Final suggestion
I believe this is happening because of the use of << on a has_many_through operation. I don't see that the << should in fact trigger your after_save event at all:
Your current code is this:
describe Thing do
before(:each) do
#user = User.create!(:name => "Fred")
#thing = Thing.create!(:name => "Foo")
#user.things << #thing
end
end
class Thing < ActiveRecord::Base
after_save :do_stuff
...
def do_stuff
followers.each { |f| puts "I'm followed by #{f.name}" }
end
end
and the problem is that the do_stuff is not getting called. I think this is the correct behaviour though.
Let's go through the RSpec:
describe Thing do
before(:each) do
#user = User.create!(:name => "Fred")
# user is created and saved
#thing = Thing.create!(:name => "Foo")
# thing is created and saved
#user.things << #thing
# user_thing_relationship is created and saved
# no call is made to #user.save since nothing is updated on the user
end
end
The problem is that the third step does not actually require the thing object to be resaved - its simply creating an entry in the join table.
If you'd like to make sure that the #user does call save you could probably get the effect you want like this:
describe Thing do
before(:each) do
#thing = Thing.create!(:name => "Foo")
# thing is created and saved
#user = User.create!(:name => "Fred")
# user is created BUT NOT SAVED
#user.things << #thing
# user_thing_relationship is created and saved
# #user.save is also called as part of the addition
end
end
You may also find that the after_save callback is in fact on the wrong object and that you'd prefer to have it on the relationship object instead. Finally, if the callback really does belong on the user and you do need it to fire after creating the relationship you could use touch to update the user when a new relationship is created.
UPDATED ANSWER **
This passes rspec, without stubbing, running callbacks for save (after_save callback included ), and checks that #thing.followers is not empty before trying to access its elements. (;
describe Thing do
before :each do
#user = User.create(:name => "Fred");
#thing = Thing.new(:name => 'Foo')
#user.things << #thing
#thing.run_callbacks(:save)
end
it "should have created a relationship" do
#thing.followers.should == [#user]
puts #thing.followers.inspect
end
end
class Thing < ActiveRecord::Base
after_save :some_function
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
def some_function
the_followers = followers
unless the_followers.empty?
puts "accessing followers here: the_followers = #{the_followers.inspect}..."
end
end
end
ORIGINAL ANSWER **
I was able to get things to work with the after_save callback, so long as I did not reference followers within the body / block of do_stuff. Do you have to reference followers in the real method you are calling from after_save ?
Updated code to stub out callback. Now model can remain as you need it, we show #thing.followers is indeed set as we expected, and we can investigate the functionality of do_stuff / some_function via after_save in a different spec.
I pushed a copy of the code here: https://github.com/kikuchiyo/RspecHasMany
And spec passing thing* code is below:
# thing_spec.rb
require 'spec_helper'
describe Thing do
before :each do
Thing.any_instance.stub(:some_function) { puts 'stubbed out...' }
Thing.any_instance.should_receive(:some_function).once
#thing = Thing.create(:name => "Foo");
#user = User.create(:name => "Fred");
#user.things << #thing
end
it "should have created a relationship" do
#thing.followers.should == [#user]
puts #thing.followers.inspect
end
end
# thing.rb
class Thing < ActiveRecord::Base
after_save :some_function
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
def some_function
# well, lets me do this, but I cannot use #x without breaking the spec...
#x = followers
puts 'testing puts hear shows up in standard output'
x ||= 1
puts "testing variable setting and getting here: #{x} == 1\n\t also shows up in standard output"
begin
# If no stubbing, this causes rspec to fail...
puts "accessing followers here: #x = #{#x.inspect}..."
rescue
puts "and this is but this is never seen."
end
end
end
My guess is that you need to reload your Thing instance by doing #thing.reload (I'm sure there's a way to avoid this, but that might get your test passing at first and then you can figure out where you've gone wrong).
Few questions:
I don't see you calling #thing.save in your spec. Are you doing that, just like in your console example?
Why are you calling t.save and not u.save in your console test, considering you're pushing t onto u? Saving u should trigger a save to t, getting the end result you want, and I think it would "make more sense" considering you are really working on u, not t.