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.
Related
I have a question about process if i could be doing this a better way. I'm working on a rails app and i run into page errors when certian elements are not present. For example here is my show action
def show
#article = Article.friendly.find(params[:article_slug])
#section_slug = Section.find_by_id(#article.section_id).slug if #article.section_id.present?
#issue_slug = Issue.find_by_id(#article.issue_id).slug if #article.issue_id.present?
#next_article = #article.next_article if #article.next_article.present?
#prev_article = #article.prev_article if #article.prev_article.present?
#article_author = Author.find_by_id(#article.author_id)
render :layout => 'magazine'
session[:return_to] = request.referer
#if request.path != article_path(#article)
#return redirect_to #article, :status => :moved_permanently
#end
end
Should i be using .present? as much as i am? is there something better that will make pages not fail so completely if an element is not present? Just trying to learn rails in a way that will lead to better code.
First, why are you not using relations? If Article already has section_id then you should be getting to the Section using #article.section. belongs_to :section should be in your Article model.
The above is true for Issue and Author as well.
In answer to your question: No, you should not be using .present? as much as you are.
Here how I would code this action:
def show
#article = Article.friendly.find(params[:article_slug])
#section_slug = #article.section.try(:slug)
#issue_slug = #article.issue.try(:slug)
#next_article = #article.next_article
#prev_article = #article.prev_article
#author = #article.author
render :layout => 'magazine'
session[:return_to] = request.referer
end
There is no reason for #prev_article = #article.prev_article if #article.prev_article.present?. If your views depend on #prev_article they will see a nil value with or without the if #article.prev_article.present?. Your view is going to have to do .present? or similar anyways.
I might actually do away with all but #article here and have my views ask the article for its author, next_article, etc.
I think so you need to do only:
#next_article = #article.next_article
#prev_article = #article.prev_article
If next_article is not present then it will be nil so with present condition and without present condition the value of #next_article and #prev_article will be nil
welcome to RoR
if #article.prev_article.present?
is the same as
if #article.prev_article
and
#article_author = Author.find_by_id(#article.author_id)
looks like you are missing out on a belongs_to relationship. If in your model you define as follows:
class Author < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :author
end
Then you can simply call
#article.author
to retrive the author object. Please do the same for section and issue.
When you want to query a database using ids, ActiveRecord allows you to do this:
#user = User.find(1)
It looks like you are trying to paginate articles. Give the Kaminari Gem (https://github.com/amatsuda/kaminari) and Will_Paginate Gem(https://github.com/mislav/will_paginate) a go, they come quite well built and maintained.
#issue_slug = Issue.find_by_id(#article.issue_id).slug if #article.issue_id.present?
In order to not error out when calling a method, the try method is apt.
#article.section.try(:slug)
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
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!
For somewhat complicated reasons, I would like to create something that works like this:
# Controller:
#comments = #page.comments # comments are threaded
# child comments still belong to #page
...
# View:
#comments.each_root {
display #comment {
indent & recurse on #comment.children
} }
# alternatives for how recursion call might work:
# first searches #comments, then actually goes to SQL
comment.in_memory.children
# only looks at #comments, RecordNotFound if not there
# better if we know #comments is complete, and checking for nonexistent
# records would be wasteful
comment.in_memory(:only).children
# the real thing - goes all the way to DB even though target is already in RAM
# ... but there's no way for find() to realize that :(
comment.children
I'm not even sure yet if this is possible, let alone a good idea, but I'm curious, and it'd be helpful.
Basically I want to redirect find() so that it looks first/only at the collection that's already been loaded, using something like a hypothetical #collection.find{|item| item.matches_finder_sql(...)}.
The point is to prevent unnecessarily complex caching and expensive database lookups for stuff that's already been loaded en masse.
If possible, it'd be nice if this played nice with extant mechanisms for staleness, association lazy loading, etc.
The nested-comments thing is just an example; of course this applies to lots of other situations too.
So... how could I do this?
You should not write something that's already in Rails itself! You can easily leverage Rails' caching methods to put your query results in Memcached (or what ever caching framework you configured):
class ArticleController < ApplicationController
def index
#articles = Rails.cache(:articles_with_comments, :expires_in => 30.minutes) do
Article.find(:all, :include => :comments)
end
end
end
BTW. the :expires_in is optional. You can leave the cache 'forever' or expire it manually.
Second example, as by my comment:
class ArticleController < ApplicationController
def index
#page = Page.find(params[:page_id]
#articles = Rails.cache([:articles_with_comments, #page.id], :expires_in => 30.minutes) do
Article.find(:all, :include => :comments, :conditions => { :page_id => #page.id})
end
end
end
This will cache the articles and comments for a given #page object.
I edited this message do to sloppiness and changes.
def student_test
#student = Student.for_test.find(params[:id]) if params[:id]
#student ||= Student.new
run_sequence :testize
end
def test_finalize
Student.transaction do
if (params[:student]) and #student.update_attributes(params[:student])
#student.test!
end
room = Room.new(:room_num => 5)
room.save
book = #student.books
book.id_num = room.id
book.save
end
end
This returns this error message:
undefined method `id_num='
Is this because there is more than 1 book record being passed into book?
Any suggestions? Thanks.
Assuming Student has a has_many relationship to Book, then #student.books is returning an array of book records, so you'll need the following:
books = #student.books
books.each do |book|
book.id_num = room.id
book.save
end
You have horrible unreadable style. I tried to clear a little what I could.
def student_test
#student = Student.for_test.find(params[:id]) if params[:id]
#student ||= Student.new
run_sequence :testize
end
def test_finalize
Student.transaction do
if (params[:student]) and #student.update_attributes(params[:student])
#student.test!
end
room = Room.new(:room_num => 5)
room.save
book = #student.book
book.id_num = room.id
book.save
end
end
Your main problem was that named scopes are like finders - you don't write #student.find(:first), but write Student.find(:first). Same here - named scope is for retrieving object from DB, to add conditions ans rest to query. And then you call finder to get objects you wanted which.
I don't know flow of your program, but I suppose that test_finalize is run from student_test, so it can use #student.
Well, what Ruby is trying to tell you is that the Book class doesn't have an accessor to write to the id_num attribute. But since it seems likely that we're talking about Active Record here, and Book actually points to a database table -- I hate to suggest the obvious, but is id_num a real field in the books table?
Of course, the Ruby error may be entirely unhelpful, and something else is going on.
Perhaps you could show us a little more context? Where does this code live? What is the data structure? (Oh, and without wishing to be rude -- please consider indenting your code!)