Rails has_many relationships when not saved (possibly ajax related) - ruby-on-rails

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.

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

Related

Understanding SQL code in a controller in rails 3.2

I am new to ruby on rails and was going to some of the code already existing in the application.
The code is as follows(books):-
def index
#books = Book
#books = #books.select("books.*,
(select count(*) from book_issues where books.id = book_issues.book_id and book_issues.return_date is null) as issued_cnt,
(select count(*) from book_holds where books.id = book_holds.book_id) as hold_cnt")
#books = #books.joins("inner join book_issues on book_issues.book_id = books.id")
#books = #books.where('book_issues.return_date is null')
#books = #books.group('books.id')
#books.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #books }
end
end
I am finding this a little difficult to understand.Why is this code being used and why not use the below code:-
def index
if params[:book_id]
#book = Book.find(:all,
:conditions => ["book_id = ? ", params[:book_id] ],
:order => "action_date ASC")
end
end
Can someone please help me with this.
Read manuals and tutorials about associations and scoping in rails.
After that you should rewrite the code to something like this:
#model
class Book < ActiveRecord::Base
# Association for BookIssue, the BookIssue model should have a 'belongs_to :book'
has_one :book_issue
# Association for BookHold, the BookHold model should have a 'belongs_to :book'
has_one :book_hold
# Scope to get not returned books, it joins all issues that don't have a return date.
# All book with a return date will be ignored.
scope :not_returned, joins(:book_issue).where(:book_issues => { return_date: nil } )
end
#controller
def index
# Use the scope mentioned in the model, to get all not returned books.
#books = Book.not_returned.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #books }
end
end
The primary problem being solved here is "generate an array of books which aren't currently checked out and pass it to the template engine to render on the page". That SQL code is handling the first part. It's unfortunate that you have to join against book_issues to see if there are any available copies, but disregarding that for right now you'd want to define a method on Book like :available? that returned true when there's at least one copy not checked out and then use that in your controller.
As a further adjustment, I'd like to have a database column on the book records that let me know if they were available for checkout without joining against the book_issues table (BooksController#Index sounds like it'd be invoked an awful lot, and you don't want it to thrash the database). That'd mean updating your book checkout logic to tweak the master book record, maybe.
The code would be much happier if it looked like this:
#books controller
def index
#books = Book.available
respond_to ... # continue as before
end
# book model
scope :available, where(copies_available: true)
# book_issue model
belongs_to :book
after_save :update_parent_availability
def available?
return_date.nil?
end
def update_parent_availability
book.copies_available = book.issues.select(&:available?).any?
book.save if book.changed?
end
That :update_parent_availability action might be subject to race conditions. You should probably factor it out into a helper book availability management class and run it in a transaction.
I think you'll find the ActiveRecord sections of the Rails guide very helpful. I'd suggest giving the ActiveRecord querying docs a thorough read. Pay close attention to the general style of the examples. One of the most powerful aspects of the MVC (Model-View-Controller) pattern is that you can build very simple interfaces within your model that do the "heavy lifting" rather than cluttering up your controllers with logic that really doesn't belong there.

Show Last modified tables/records Rails 3.2

I still can't figure out how to implement this as i am a newbie with this. some people helpt me and said i had to use audited, so it did. this is my controller:
def show
add_breadcrumb 'Contract Bekijken', :contracten_path
#contracten = Contracten.find(params[:id])
#audits = #contracten.audits.collect { |a| a.created_at }
respond_to do |format|
format.html # show.html.erb
format.json { render json: #contracten }
end
end
Here's a pastie of my whole controller. http://pastie.org/4270702
But i don't know if this is right or how to implement this to my views.
I hope someone really can help because i really need this to work this week.
Thanks.
i have a rails app where i can store contracts in a database, it also has persons and factories tables in the database.
Now i would like to have a last modified table.
I would like when people update/add a new record to the database, that it will show the modifications in the div right on the screenshot.
Thanks :D
What you need is a audit history of edits.
You can either implement that yourself (may make sense if there is custom business logic involved you really want to write yourself) by hooking into the ActiveRecord callbacks for that model.
A sample implementation may look like this:
class Contract < ActiveRecord::Base
after_save :audit
def audit
log = AuditLog.new(:user_id => current_user, :action => :update, ...)
log.save
end
end
This would assume you have a AuditLog Model that contains the appropriate fields you want to log to and in your audit method you write to that.
Or, a simpler way is to use the Audited gem that does that for you (but may come with some limitations).
From the documentation of Audited it seems like you can simply fetch a Contract record and use the audits on that model to then access the audited information.
Sample
def show
#contract = Contract.find(params[:id])
#audits = #contract.audits.collect { |a| a.created_at }
end
Now you have all the timestamps of the audits in the #audits variable and can access them from the view using <% #audits.each do ....
From your question it seems like you just need a list based on the updated_at field.
How about - #contract_list = Contract.all.order( "updated_at DESC").limit(10)
Then you can iterate over the list in the view.
Nice looking page!

How to mock a before filter variable assignment? ActionController::TestCase

The idea is as follows: when visiting a purchase page, the pre-initialized (using before_filter) #purchase variable receives save if the item in question is not free.
I make two gets, one for a paid item and one for a free item. purchase.expects(:save).returns(true) expects :save to be called only once, so the below test works.
But this is really really ugly. The test it incredibly lengthy. What would be a better way to do this? Should I mock the find_or_initialize method? If so, how would I set the #purchase instance variable?
Sorry for the ugly code below...
def test_new_should_save_purchase_if_not_free
user = users(:some)
purchase = user.purchases.build
#controller.stubs(:current_user).returns(user)
purchases_mock = mock
user.stubs(:purchases).returns(purchases_mock)
purchases_mock.stubs(:build).returns(purchase)
purchase.expects(:save).returns(true)
get :new, :item_id => items(:not_free).id, :quantity => 10
get :new, :item_id => items(:free).id, :quantity => 400
end
def new
#purchase.quantity = params[:quantity]
#purchase.item = Item.find(params[:item_id])
unless #purchase.item.free?
#purchase.save
end
end
def find_or_initialize
#purchase = params[:id] ? current_user.purchases.find(params[:id]) : current_user.purchases.build
end
It looks like you're already using fixtures for your items, why not just use a fixture for the Purchase as well? There is no need to go through all that effort to stub the user.

undefined method error, but I defined it!

Rails newbie here, trying to get a new controller working.
When I try to show ann existing instance, I get an undefined method error on a helper method.
Code follows.
Any idea why getRecipes would be undefined?!
Controller:
def show
id = params[:id]
recipe_ids = ConcreteMenu.getRecipes(id)
respond_to do |format|
format.html
end
end
Model
require 'json/objects'
class ConcreteMenu < ActiveRecord::Base
has_many :menu_recipes
has_many :recipes, :through => :menu_recipes
belongs_to :menu
def self.getRecipes(id)
recipes = MenuRecipe.find(:all, :conditions => {:concrete_menu_id => id}, :select => 'id')
end
end
It would help if you pasted the error text, because your explanation leaves a lot of possibilities for what could be wrong. BUT, there is an easier way to get what you want. The value of defining "has_many" relationships is that instead of calling a class method and passing the id of a concrete menu to get its associated recipes, you can just do this:
def show
#concrete_menu = ConcreteMenu.find(params[:id], :include => :recipes)
end
Now you'll have the menu object, and #concrete_menu.recipes returns an array of recipes you need. This feature is already built in, no need to reinvent the wheel.
Also, I noticed you were attempting to collect id's in the controller instead of the objects themselves. This suggests that you're going back and actually retrieving the records in the view itself. This is less efficient, and more difficult to troubleshoot when things go wrong. My example above will do what you need in a better (and more rails-accepted) way.
As you have it defined there, it should be available. Is there a chance you have something else called ConcreteMenu defined, but in a different context?
To be sure you're calling the correct one, where there may be ambiguity, you can refer to the top-level class:
recipe_ids = ::ConcreteMenu.getRecipes(id)
The other way to check that the method is defined correctly via script/console:
ConcreteMenu.methods.grep(/getRecipe/)
# => ["getRecipes"]
This is presuming, of course, you're having trouble with the getRecipes method. There's a possibility you're mistaking how controller variables are passed to the view:
def show
#id = params[:id]
#recipe_ids = ConcreteMenu.getRecipes(#id)
respond_to do |format|
format.html
end
end
Any instance variables defined (#...) will be available within the context of the view, but any local variables will no longer be defined as they are out of scope.

How can I pass objects from one controller to another in rails?

I have been trying to get my head around render_to but I haven't had much success.
Essentially I have controller methods:
def first
#I want to get the value of VAR1 here
end
def second
VAR1 = ["Hello", "Goodbye"]
render_to ??
end
What I can't figure out is how to accomplish that. Originally I just wanted to render the first.html.erb file but that didn't seem to work either.
Thanks
Edit: I appreciate the answers I have received, however all of them tend to avoid using the render method or redirect_to. Is it basically the case then that a you cannot pass variables from controller to controller? I have to think that there is some way but I can't seem to find it.
It is not a good idea to assign the object to a constant. True this is in a global space, but it is global for everyone so any other user going to this request will get this object. There are a few solutions to this.
I am assuming you have a multi-step form you are going through. In that case you can pass the set attributes as hidden fields.
<%= f.hidden_field :name %>
If there are a lot of fields this can be tedious so you may want to loop through the params[...] hash or column_names method to determine which attributes to pass.
Alternatively you can store attributes in the session.
def first
#item = Item.new(params[:item])
session[:item_attributes] = #item.attributes
end
def second
#item = Item.new(session[:item_attributes])
#item.attributes = params[:item]
end
Thirdly, as Paul Keeble mentioned you can save the model to the database but mark it as incomplete. You may want to use a state machine for this.
Finally, you may want to take a look at the Acts As Wizard plugin.
I usually don't have my controllers calling each other's actions. If you have an identifier that starts with a capital letter, in Ruby that is a constant. If you want to an instance level variable, have it start with #.
#var1 = ["Hello", "Goodbye"]
Can you explain what your goal is?
Have you considered using the flash hash? A lot of people use it solely for error messages and the like, it's explicitly for the sort of transient data passing you might be interested in.
Basically, the flash method returns a hash. Any value you assign to a key in the hash will be available to the next action, but then it's gone. So:
def first
flash[:var] = ["hello", "goodbye"]
redirect_to :action => :second
end
def second
#hello = flash[:var].first
end
way 1
Global variable
(fail during concurrent requests)
way 2
class variable
(fail during concurrent requests)
way 3
Stash the object on the server between requests. The typical way is to save it in the session, since it automatically serializes/deserializes the object for you.
Serialize the object and include it in the form somewhere, and
deserialize it from the parameters in the next request. so you can store attributes in the session.
def first
#item = Item.new(params[:item])
session[:item_attributes] = #item.attributes
end
def second
#item = Item.new(session[:item_attributes])
#item.attributes = params[:item]
end
way 4
The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed to the very next action and then cleared out.
def new
#test_suite_run = TestSuiteRun.new
#tests = Test.find(:all, :conditions => { :test_suite_id => params[:number] })
flash[:someval] = params[:number]
end
def create
#test_suite_run = TestSuiteRun.new(params[:test_suite_run])
#tests = Test.find(:all, :conditions => { :test_suite_id => flash[:someval] })
end
way 5
you can use rails cache.
Rails.cache.write("list",[1,2,3])
Rails.cache.read("list")
But what happens when different sessions have different values?
Unless you ensure the uniqueness of the list name across the session this solution will fail during concurrent requests
way 6
In one action store the value in db table based on the session id and other action can retrieve it from db based on session id.
way 7
class BarsController < UsersController
before_filter :init_foo_list
def method1
render :method2
end
def method2
#foo_list.each do | item|
# do something
end
end
def init_foo_list
#foo_list ||= ['Money', 'Animals', 'Ummagumma']
end
end
way 8
From action sent to view and again from view sent to other actions in controller.

Resources