Show Last modified tables/records Rails 3.2 - ruby-on-rails

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!

Related

How to display an specific item of database

My rails app has a database set.
def index
#clubs = Club.all
end
This is my controller.
If i type in my Index.html.erb
<% #clubs.each do |club| %>
<%= club.name %>
<% end %>
I get all the names of my database show in my index view.
What if I just want to pick one or just a couple?
Thru the rails console i can by typing c=Club.find(1) 1 by default takes id=1.
So how can i just display several ID's and not all one the database in the same index.html.erb.
thanks anyway!
Try this:
Let us consider that params[:ids] contains all the ids that belong to the records you want to get.
def index
#clubs = Club.where(id: params[:ids])
end
Fix
The straightforward answer here is to recommend you look at the ActiveRecord methods you can call in your controller; specifically .where:
#app/controllers/clubs_controller.rb
Class ClubsController < ApplicationController
def index
#clubs = Club.where column: "value"
end
end
This will populate the #clubs instance variable with only the records which match that particular condition. Remember, it's your Rails app, so you can do what you want with it.
Of course, it's recommended you stick with convention, but there's nothing stopping you populating specific data into your #clubs variable
--
RESTful
As someone mentioned, you shouldn't be including "filtered" records in an index action. Although I don't agree with this idea personally, the fact remains that Rails is designed to favour convention over configuration - meaning you should really leave the index action as showing all the records
You may wish to create a collection-specific action:
#config/routes.rb
resources :clubs do
collection do
get :best #-> domain.com/clubs/best
end
end
#app/controllers/clubs_controller.rb
Class ClubsController < ApplicationController
def best
#clubs = Club.where attribute: "value"
render "index"
end
end
There are several ways to select a specific record or group of records from the database. For example, you can get a single club with:
#club = Club.find(x)
where x is the id of the club. Then in your view (the .html.erb file), you can simply access the #club object's attributes.
You can also cast a wider net:
#disco_clubs = Club.where(type: "disco") # returns an ActiveRecord Relation
#disco_clubs = Club.where(type: "disco").to_a # returns an array
And then you can iterate over them in the same manner you do in your index.html.erb. Rails has a rich interface for querying the database. Check it out here.
Also note that individual records - such as those selected with the find method - are more commonly used with the show action, which is for displaying a single record. Of course, that's for generic CRUD applications. It't not a hard rule.
change
def index
#clubs = Club.all
end
to this
def index
#clubs = Club.find(insert_a_number_that_is_the_id_of_the_club_you_want)
end
Querying your database is a complex thing and gives you a ton of options so that you can get EXACTLY what you want and put it into your #clubs variable. I suggest reading this part of the rails guide
It should also be noted that if you're only going to query your database for one record then change #clubs to #club so you know what to expect.

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.

Can I make Rails update_attributes with nested form find existing records and add to collections instead of creating new ones?

Scenario: I have a has_many association (Post has many Authors), and I have a nested Post form to accept attributes for Authors.
What I found is that when I call post.update_attributes(params[:post]) where params[:post] is a hash with post and all author attributes to add, there doesn't seem to be a way to ask Rails to only create Authors if certain criteria is met, e.g. the username for the Author already exists. What Rails would do is just failing and rollback update_attributes routine if username has uniqueness validation in the model. If not, then Rails would add a new record Author if one that does not have an id is in the hash.
Now my code for the update action in the Post controller becomes this:
def update
#post = Post.find(params[:id])
# custom code to work around by inspecting the author attributes
# and pre-inserting the association of existing authors into the testrun's author
# collection
params[:post][:authors_attributes].values.each do |author_attribute|
if author_attribute[:id].nil? and author_attribute[:username].present?
existing_author = Author.find_by_username(author_attribute[:username])
if existing_author.present?
author_attribute[:id] = existing_author.id
#testrun.authors << existing_author
end
end
end
if #post.update_attributes(params[:post])
flash[:success] = 'great!'
else
flash[:error] = 'Urgg!'
end
redirect_to ...
end
Are there better ways to handle this that I missed?
EDIT: Thanks for #Robd'Apice who lead me to look into overriding the default authors_attributes= function that accepts_nested_attributes_for inserts into the model on my behalf, I was able to come up with something that is better:
def authors_attributes=(authors_attributes)
authors_attributes.values.each do |author_attributes|
if author_attributes[:id].nil? and author_attributes[:username].present?
author = Radar.find_by_username(radar_attributes[:username])
if author.present?
author_attributes[:id] = author.id
self.authors << author
end
end
end
assign_nested_attributes_for_collection_association(:authors, authors_attributes, mass_assignment_options)
end
But I'm not completely satisfied with it, for one, I'm still mucking the attribute hashes from the caller directly which requires understanding of how the logic works for these hashes (:id set or not set, for instance), and two, I'm calling a function that is not trivial to fit here. It would be nice if there are ways to tell 'accepts_nested_attributes_for' to only create new record when certain condition is not met. The one-to-one association has a :update_only flag that does something similar but this is lacking for one-to-many relationship.
Are there better solutions out there?
This kind of logic probably belongs in your model, not your controller. I'd consider re-writing the author_attributes= method that is created by default for your association.
def authors_attributes=(authors_attributes)
authors_attributes.values.each do |author_attributes|
author_to_update = Author.find_by_id(author_attributes[:id]) || Author.find_by_username(author_attributes[:username]) || self.authors.build
author_to_update.update_attributes(author_attributes)
end
end
I haven't tested that code, but I think that should work.
EDIT: To retain the other functionality of accepts_nested_Attributes_for, you could use super:
def authors_attributes=(authors_attributes)
authors_attributes.each do |key, author_attributes|
authors_attributes[key][:id] = Author.find_by_username(author_attributes[:username]).id if author_attributes[:username] && !author_attributes[:username].present?
end
super(authors_attributes)
end
If that implementation with super doesn't work, you probably have two options: continue with the 'processing' of the attributes hash in the controller (but turn it into a private method of your controller to clean it up a bit), or continue with my first solution by adding in the functionality you've lost from :destroy => true and reject_if with your own code (which wouldn't be too hard to do). I'd probably go with the first option.
I'd suggest using a form object instead of trying to get accepts_nested_attributes to work. I find that form object are often much cleaner and much more flexible. Check out this railscast

GET params in ruby-on-rails project - best practices?

I've inherited a little rails app and I need to extend it slightly. It's actually quite simple, but I want to make sure I'm doing it the right way...
If I visit myapp:3000/api/persons it gives me a full list of people in XML format. I want to pass param in the URL so that I can return users that match the login or the email e.g. yapp:3000/api/persons?login=jsmith would give me the person with the corresponding login. Here's the code:
def index
if params.size > 2 # We have 'action' & 'controller' by default
if params['login']
#person = [Person.find(:first, :conditions => { :login => params['login'] })]
elsif params['email']
#persons = [Person.find(:first, :conditions => { :email => params['email'] })]
end
else
#persons = Person.find(:all)
end
end
Two questions...
Is it safe? Does ActiveRecord protect me from SQL injection attacks (notice I'm trusting the params that are coming in)?
Is this the best way to do it, or is there some automagical rails feature I'm not familiar with?
Yes, the code you listed should be safe from SQL Injection.
Yes, this is generally acceptable rails code...but
There are some oddities.
Your index action talks about #person and #persons. By convention, #persons is expected, and #person is unusual. I suspect you can eliminate #person, and clear things up in one swoop. Something like this (untested):
def index
#persons = if params[:email]
Person.find_all_by_email(params[:email])
elsif params[:login]
Person.find_all_by_login(params[:login])
else
Person.all
end
end
Don't forget to update your view -- I suspect it's still looking for #person. If your view is doing anything "interesting" with #person, you probably want to move it to your show action.
Here's the ruby on rails security guide section on SQL Injection. It looks like what you have, using hash conditions, is pretty safe. Sounds like using Person.find_by_login or Person.find_by_email might be a little better.

Overriding Rails to_param?

How do I get the to_param method to deliver keyword slugs all the time? I have trouble getting it to work with this route:
map.pike '/auction/:auction_id/item/:id', :controller => 'items', :action => 'show'
Earlier the overridden to_param was working for
'items/1-cashmere-scarf'
but fails with 'auction/123/item/1'
Update:
I'm not sure if the syntax is correct[(edit: it's correct: it works :-)], or even efficient.... but using haml, I found that the following code works to generate the desired link ('auction/:auction_id/item/:id')
- for auction in #auctions.sort{|a, b| a.scheduled_start <=> b.scheduled_start}
-for item in #items
- unless auction.current_auction
... pike_path(auction.auction_id, item)
I'm not sure whether I understand your question. (it's 3:41 AM here)
From what I see, you directly access auction_id method, instead of using pike_path(auction, item) that'd use #to_param.
Also, it might fail for auction/123/item/1 because you haven't changed your controller.
I think it'd be helpful to describe how to get working slugs.
Broadly speaking, if you override #to_param, IDs no longer works. It means, that if you go with slugs, every time polymorpic URL is generated (eg, link_to object, object), it passes to_param's value. It is worth noting that you must change your controller as well.
Personally I think that the best way to generate slugs easily is to use techno-weenie's permalink_fu, adding has_permalink to your model, and then, override to_param. For example
class Auction < ActiveRecord::Base
has_permalink :title, :slug
end
assuming that you have slug, a string field, and want to slugize your title.
You also need to adjust your controller:
class AuctionsController < ApplicationController
def show
#auction = Auction.find_by_slug(params[:id]) || raise(ActiveRecord::RecordNotFound)
respond_to do |format|
format.html # show.html.erb
end
end
Then, you can generate routes, in the views, this way:
link_to #action, #action
By the way, you should NOT sort your actions in the view. The best way is to use named_scope.

Resources