Getting previous HABTM values - ruby-on-rails-3

In my app, I have several clients, and they have several elements (via has_many_through association) depending on a certain BusinessType to which Client belongs to so that instead of manually adding all the elements to the Client, I can just select the BusinessType and everything gets added automatically (business_type in Client is attr_readonly). BusinessType HABTM elements.
Here's the catch, after creation with the default BusinessType, the clients can update their elements and remove or add as they please (mostly add), so what I'm trying to do is the following:
Suppose one business_type has elements [1,2,3] and is assigned to one client, then, the following elements are added manually to the client = [4,5,6] so it ends up having [1,2,3,4,5,6], ok everything's fine here.
But after this, the business_type gets updated and has element 2 removed, so it ends up being [1,3]. Here's the deal, I want the client to be updated by removing the 2, but not the [4,5,6] that do not correspond to the business_type in question so that it ends up [1,3,4,5,6], I'm using an after_update callback to update the clients' elements but the _was method doesn't work for HABTM relationships (to get the old business_type's elements.
I've tried using a before_update callback to first to client.elements = client.elements - business_type.elements to store momentarily in the DB [1,2,3,4,5,6] - [1,2,3] = [4,5,6], and in the after_update do client.elements = client.elements + business_type.elements to get [4,5,6] + [1,3] = [1,3,4,5,6]but this has already the new value of [1,3]. How can I get the old business_type.elements value in the before_update or after_update?
Thanks in advance for your help!

I had a similar problem in an app, and the only solution I could come up with was to store the values before doing update_attributes in the controller.
Example code:
Models
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories, :join_table => "categories_products"
def remember_prev_values(values)
#prev_values = values
end
def before_update_do_something
puts #prev_values - self.category_ids # Any categories removed?
puts self.category_ids - #prev_values # Any categories added?
end
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :products, :join_table => "categories_products"
end
In the update method in the products controller I do the following:
class ProductsController < ApplicationController
...
def update
#product.remember_prev_values(#product.category_ids)
if #product.update_attributes(params[:product])
flash[:notice] = "Product was successfully updated."
redirect_to(product_path(#product))
else
render :action => "edit"
end
end
...
end
It is not ideal, but it is then possible to "catch" the habtm inserts/removes before they are executed.
I do think it is possible to do in a callback, but you might need to "hack" into ActiveRecord.
I did not spend much time on trying to dig into ActiveRecord internals, as this is a simple implementation that works.

You should use after_initialize callback to store previous values.
after_initialize do #previous_elements = elements.map{|x| x} end
Note that here we make a copy of assosiations by map function call.

Related

Adding integer to column in database

I have an integer column on the Users table called rating_number. This number is going to consist of two things.
Impressions on page views
The total number of likes they have on their posts
So far, I have the impression part taken care of. I'm using the gem is_impressionable with a counter_cache like so on my User model:
is_impressionable :counter_cache => true, :column_name => :rating_number, :unique => :all
Now, I'm trying to add to that column the second part, which is the total number of votes they have on their posts. I am getting that integer by:
#user = current_user # or some user
array = #user.posts.map { |post| post.votes.count }
count = array.inject { |sum, x| sum + x }
where count is the total number of votes they have on their posts. How can I automatically update the rating_number column in an efficient way every time a User get's one of their posts voted_on. Should I instead go the direction where I manually add 1 to that column in the post's def vote action after the vote has successfully been saved?
Not sure if this is useful, but I'm also using the thumbs_up gem for voting system.
Lookig at the your need, I am quite sure you need to use callback called after_update in your User model. To understand how call back works, read Callbacks. But I would suggest you to keep the data in 2 separate columns, rather than a single column.
class User < ActiveRecord::Base
after_update :vote_update
# other methods
def vote_update
user = #post.user
user.rating_number = user.rating_number + 1
user.save!
end
end

Rails: create lots of almost-duplicate records

I want to write a method that creates a bunch of almost-duplicate records, just with one or two parameters changed. I'll make a form to control those parameters, I'm just wondering about how best to write the method, and where do keep it.
Presently in my document.rb I've written this:
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create(:component_ids => components, :message => 'Message.', :template_id => template.id, :user_id => user.id)
end
end
It doesn't feel right though. Is there a better way to do this?
This code is fine if your security model allows all these fields to be bulk assignable by mention in attr_accessible in the model. If it doesn't then you're better off using the block form of create. Also, if Document, Template and User are ActiveRecord instances, you should let Rails manage the details of ids.
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create do |doc|
doc.component_ids = components,
doc.message 'Message.',
doc.template = template,
doc.user = user
end
end
end
One final note is that component_ids must be serialized to store a list. This is probably a flaw in your model design. The better way is (probably) to specify Component belongs_to User and also User has_many Components. I.e. Component contains a foreign key to User. If it's necessary for a Component to belong also to many users, then you'll need either has_and_belongs_to_many or has_many ... through. The Rails guide on relations describes all this in more detail.
With the right relations set up, the code will become:
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create do |doc|
doc.components = components, # Components is now a list of active records.
doc.message 'Message.',
doc.template = template,
doc.user = user
end
end
end
The resulting SQL will get all the foreign keys and (if necessary) relation tables filled in correctly.

rails before_destroy partial delete

I'm trying to figure out a way to partially delete/destroy dependent models in rails.
Code looks something like this:
class User < ActiveRecord::Base
has_many :subscriptions
has_many :photos, :dependent => :destroy
has_many :badges, :dependent => :destroy
before_destroy :partial_destroy
def partial_destroy
self.photos.destroy_all
self.badges.destroy_all
return false if self.subscriptions.any?
end
...
Essentially, I want to destroy the photos and badges, but if the user has any subscriptions, I want to keep those, and also keep the user from being destroyed.
I tried with .each { |obj| obj.destroy } and using delete and delete_all, but it seems to not matter.
It looks like rails is performing some kind of a rollback whenever the before_destroy returns false. Is there a way to destroy part of the dependents but not others?
This is old so I expect you've forgotten it, but I stumbled across it.
I'm not surprised delete and delete_all didn't work, since those bypass callbacks.
You're exactly right that Rails performs a rollback if any before_ callback returns false. Because Rails wraps the entire callback chain in a transaction, you're not going to be able to perform database calls (like destroys) inside the chain. What I would recommend is putting a conditional in the callback:
If the user has subscriptions, kick off a background job which will do this partial delete later (outside the callback transaction), and return false from the callback.
If they don't have subscriptions, you don't start the background job, return true from the callback, and destroy your model as usual.
I ended up doing the following:
override destroy on the User model (see below)
not actually deleting the User, but rather destroying the dependants that are not needed, and blanking any fields on the User model itself, e.g. email.
I created a UserDeleter class that takes the user and performs all clearing operations, just to keep things cleaner / having some kind of single-responsibility
overriding destroy
def destroy
run_callbacks(:destroy) do
UserDeleter.new(self).delete
end
end
deleting dependants and clearing data on User
class UserDeleter
def initialize(user)
#user = user
end
def delete
delete_photos
delete_badges
clear_personal_data
# ...
end
private
def delete_photos
#user.photos.destroy_all
end
def clear_personal_data
#user.update_attributes!(
:email => deleted_email,
:nickname => '<deleted>')
end
def deleted_email
"deleted##{random_string}.com"
end
def random_string(length = 20)
SecureRandom.hex(length)[0..length]
end
#...
end

Best way to update elements from an has_many relationship without instantiating full models and triggering the callbacks

This is something I'm trying to do now: A has_many Bs. B has certain callbacks that need to be triggered. Just, when I save from A, I want something to be updated in Bs. Since the Bs can be 10000, I would like not to load them into memory, and still have the benefit of seeing the callbacks triggered. What is the best strategy for this?
And please, no "find_each" or any find by batches variant, I'm aware of them and they will be my last resort in case nothing else works.
When I encountered this problem, I use this solution
define "callback methods" in a class and use they with ids,
define really callbacks in a instance and pass id of record in "class callback"
example of code:
class Post < AR
has_many :comments
after_save do |post|
Post.recalculate_counters(post.comment_ids)
end
end
class Comment < AR
belongs_to :post
after_save :recalculate_couters
def self.recalculate_couters(ids)
... huge and slow update statement ...
end
def recalcuate_couters
self.class.recalculate_couters([id])
end
end
I don't think there's any way you can have the callbacks executed without loading the models. However, if you give up using callback you can use update_all that performs really fast.
You just use:
B.update_all({:timestamp => Time.now}, { :a_id => id })

Rails 3 subsequent forms submission (second dependent on first)

I am trying to achieve a subsequent form submission. To clarify things -
I submit a form for #post
then once that #post is created I would immediately (under the hood) like to submit the form for #associations.
The catch is, this second form submission would require the post_id field from the newly created #post.
What would be the best way to achieve this? Would nested forms help me pull the newly created #post.id? Kindly help me with this.
If this is something that should happen whenever you create a Post, then you should use active callbacks to achieve that :
class Post < ActiveRecord::Base
after_create do |post|
# create your association using post.id
end
end
or, you can write it like that also :
class Post < ActiveRecord::Base
after_create :after_create_post
def after_create_post
# create your association using self.id
end
end
Otherwise, if this is something specific to a controller action, you should simple do something like this :
class PostsController < ApplicationController
def create
#post = current_user.posts.build(params[:post])
# then use the #post.id to build your association. something like
#post.associations.build(:prop1 => 'value1', :prop2 => 'value2')
end
end
Hope this helps!