7

I want to preview what the model will look like when saved without currently saving to the database.

I am using @event.attributes = because that assigns but does not save attributes for @event to the database.

However, when I also try to assign the audiences association, Rails inserts new records into the audiences_events join table. Not cool. Is there a way to preview what these new associations will look like without inserting into the join table?

Model

class Event < ActiveRecord::Base
  has_and_belongs_to_many :audiences # And vice versa for the Audience model.
end

Controller

class EventsController < ApplicationController 

  def preview
    @event = Event.find(params[:id])
    @event.attributes = event_params
  end

  private
    def event_params
      params[:event].permit(:name, :start_time, :audiences => [:id, :name]
    end

end

Possible Solutions?

Possible solutions that I thought of, but don't know how to do:

  • Using some sort of method that assigns associations, but does not persist them.
  • disabling all database writes for this one action (I dont know how to do that).
  • Rolling back all database changes at the end of this action

Any help with these would be great!

UPDATE:
After the reading the great answers below, I ended up writing this service class that assigns the non-nested attributes to the Event model, then calls collection.build on each of the nested params. I made a little gist. Happy to receive comments/suggestions.

https://gist.github.com/jameskerr/69cedb2f30c95342f64a

James Kerr
  • 316
  • 2
  • 9
  • 1
    If you do `Event.new(name: 'some name').audiences.build(name:c 'some audience')` without calling save, the Event and Audience records are not supposed to be stored in the DB (calling `.persisted?` on each record should return `false`) (but I am curious about the join record, the one containing the Event id and the Audience id) – MrYoshiji May 07 '15 at 19:08

3 Answers3

6

In these docs you have:

When are Objects Saved?

When you assign an object to a has_and_belongs_to_many association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.

If you want to assign an object to a has_and_belongs_to_many association without saving the object, use the collection.build method.

Here is a good answer for Rails 3 that goes over some of the same issues

Rails 3 has_and_belongs_to_many association: how to assign related objects without saving them to the database

Community
  • 1
  • 1
george
  • 153
  • 7
4

Transactions

Creating transactions is pretty straight forward:

Event.transaction do
  @event.audiences.create!
  @event.audiences.first.destroy!
end

Or

@event.transaction do
  @event.audiences.create!
  @event.audiences.first.destroy!
end

Notice the use of the "bang" methods create! and destroy!, unlike create which returns false create! will raise an exception if it fails and cause the transaction to rollback.

You can also manually trigger a rollback anywhere in the a transaction by raising ActiveRecord::Rollback.

Build

build instantiates a new related object without saving.

event = Event.new(name: 'Party').audiences.build(name: 'Party People')
event.save # saves both event and audiences
max
  • 96,212
  • 14
  • 104
  • 165
  • Which you use depends on your needs - if you need to guarantee that any callbacks are successful or database constraints (such as unique indices) are met than you want transactions. – max May 07 '15 at 19:42
0

I know that this is a pretty old question, but I found a solution that works perfectly for me and hope it could save time to someone else:

class A
  has_many :bs, class_name 'B'
end

class B
  belongs_to :a, class_name: 'A'
end

a.bs.target.clear
new_bs.each {|new_b| a.bs.build new_b.attributes.except('created_at', 'updated_at', 'id') }

you will avoid autosave that Rails does when you do a.bs = new_bs