Frage

Have a quick question about the has_many and belongs to. I'm in a situation where a :user has_many :accounts, and an :account belongs_to the user.

In my controller, I first assign @accounts = current_user.accounts. Debugging [correctly] reports to me that this particular use has 2 accounts. In the next line, I save an invalid account (debugging also tells me correctly that it is invalid). However, when I inspect current_user.accounts.count versus current_user.accounts.all.count versus current_user.accounts.all(order: 'created_at DESC'), I get the following values:

- current_user.accounts.count = 2
 - current_user.accounts.all.count = 3
 - current_user.accounts.all(order: 'created_at DESC') = 2

Inspection of the database confirms that the invalid model indeed did not get saved.

Furthermore, in my dynamically ajax re-loaded view to which I feed @accounts = current_user.accounts (which is set after the if-else loop checking if @account.save worked), it loops through and displays 3 accounts, including the invalid account.

Here's the code for the controller:

def create

    @account = current_user.accounts.new(account_params) 

    if @account.save
      @accounts = current_user.accounts.all(order: 'created_at DESC')
      #redirect_to accounts_path, :success => "Account successfully created."
      flash[:success] = "Account sucessfully created."
      # respond_to :js
      respond_to do |format|
        format.js {
          render :create
        }
      end


    else
      @accounts = current_user.accounts.all(order: 'created_at DESC')

      flash[:error] = "There was a problem with adding your account."
      respond_to do |format|
        format.js {
          render :create
        }
      end

    end
    puts "Final Accounts is #{@accounts.count} compared to #{current_user.accounts.all.count} compared to #{current_user.accounts.count}" # outputs 2, 3, and 2 for an invalid model being saved
  end

Can someone explain to me the proper way I should be doing this? Or better yet, what is going on under the Rails engine? I feel sheepishly noob for having this issue.

How do I tell rails to only load the current_user.accounts that are saved in the db? Is this eager-loading related?

I'm running on Rails 4, with postgresql if this makes a difference.

War es hilfreich?

Lösung

The reason of confusion is a CollectionProxy in ActiveRecord::Associations (things are bit more complicated, than they looked):

current_user.accounts is a CollectionProxy, not an Array.

Think of CollectionProxy as of container, which internally has @owner as current_user, @target as set of accounts and @reflection - kinda links (associations) between @owner and @target

When you run current_user.accounts.new() - you just add another object into @target, so iterating over current_user.accounts you're iterating over @target, which contains objects including newly created.

But wait, why does .count return less objects?

Because Rails are lazy and @target is not loaded until you really need its objects(=accounts). So to just run .count it's cheaper to run direct SQL-request instead of instantiation of all objects in @target and then count 'em.

That's why when you do current_user.accounts.count you get amount of saved objects. And when you do current_user.accounts.all.count - it instantiate all objects in @target, convert 'em into Array and count accounts in this array (equal to current_user.accounts.size).

BTW, all is deprecated in Rails 4, use to_a instead

So, what do I do with all this knowledge, man? I just need to show accounts without unsaved one.

Just force reload: @accounts = current_user.accounts(force_reload = true)

OR @accounts = current_user.accounts.reload

OR @accounts = current_user.accounts.order('created_at DESC') It will run reload automagically because order needs direct request to objects via SQL

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top