Appendix B. Tidbits

This appendix contains a collection of tidbits that we just couldn’t fit into the rest of the book. They are designed to be read by themselves, although you are welcome (and encouraged!) to read as many of them at once as you wish.

B.1 Prettying URLs

In your application you’ve got a URL such as /projects/1/tickets/2, which really isn’t that pretty. Let’s take a look at how you can make the ticket’s ID part of these URLs a little prettier, turning them into something like /projects/1/tickets/2-make-it-shiny.

Having URLs such as /projects/1/tickets/2-make-it-shiny will allow your users to easily identify what that URL links to without having to visit the page.[1] Rails allows you to very easily do this by overriding a method called to_param in the model where you want to have pretty URLs. This method is defined like this within ActiveRecord::Base, and so all your models inherit it:

1 It’s also supposedly good for search engine optimization (SEO).

 def to_param
  id
end

This method is used to construct the URLs generated by the link helpers in your application such as project_ticket_path, if you use them like this:

project_ticket_path(project, ticket)

Rails does not care what the classes of the objects that are passed in here are; it just assumes you know what you’re doing. What it does care about though is the output of the to_param method, which must not contain any slashes. To get the pretty URL you always wanted you can use the parameterize method on any String, which works like this:

  "Make it shiny!".parameterize
= "make-it-shiny"

It does this by substituting any character that isn’t a through z, the digits 0 through 9, a hyphen (-), or an underscore (_) into the - character. If the string ends in one of these invalid characters then it will be removed completely. By doing this, it makes the name safe to be used as a parameter in the URL, hence the name parameterize.

Before you actually make this change, you’re going to write a test for it to make sure that this feature of your code isn’t accidentally changed or removed. Because this change is going to be for your Ticket model, you’re going to put the test for it in spec/models/ticket_spec.rb, using the code from the following listing:

Listing B.1. spec/models/ticket_spec.rb
require 'spec_helper'

describe Ticket do
 it "has pretty URLs" do
   ticket = Factory(:ticket, :title = "Make it shiny!")
   ticket.to_param.should eql("#{ticket.id}-make-it-shiny")
 end
end

This code will use the factory you created in chapter 7 to create a new ticket with the title “Make it shiny!” Then you call to_param on that ticket, and you expect to see it output the ticket’s id followed by the parameterized version of “Make it shiny”: make-it-shiny.

When you run this test with bin/rspec spec/models/ticket_spec.rb, you’ll see the following output:

expected "1-make-it-shiny"
    got "1"

This is happening because you haven’t yet overridden the to_param method in your Ticket model and it is defaulting to just providing the id. To fix this, you can open the app/models/ticket.rb and add in the new method:

 def to_param
  "#{id}-#{title.parameterize}"
end

When you run our test again, it will now be green:

1 example, 0 failures

You don’t have to change anything else because Rails is still so incredibly darn smart! For instance, you can pass this “1-make-it-shiny” string to the find method, and Rails will still know what to do. Go ahead, try this:

Ticket.find("1-make-it-shiny")

If you have a record in the tickets table with an id value of 1, Rails will find this. This is because Rails will automatically call to_i on string arguments passed to find. To see this in action, you can do this in an irb session:

"1-make-it-shiny".to_i

You should get back the number 1 from this. If you didn’t have the id prefix in your routes and instead had something like make-it-shiny, you would need to save this string to a field in the table called something obvious like permalink by using a before_create method; then rather than using find in places where you are searching for an object, you would instead use find_by_permalink.

B.2 Attribute change tracking

When working with Active Record, you have the ability to check if a field has changed since the last time you made reference to this record. Let’s try this now with a Project object in the ticketee project by first launching rails console.

Within this console, let’s create a new Project object:

>> project = Project.new

On any ActiveRecord::Base descendant, you can call the changed? method to determine if it has changed since it was created or found. Let’s call it:

 >> project.changed?
=> false

In this case, the project hasn’t changed from when it was created and so the changed? method returns false. If you set any attribute on this object, the changed? method will return true:

 >> project.name = "Ticketee"
=> "Ticketee"
>> project.changed?
=> true

Now if you want to know what fields have caused this change, you can just drop off the question mark at the end of changed?, like this:

 >> project.changed
=> ["name"]

As you can see here, this method returns an array containing the attributes which have changed on this object. If you changed another attribute on this project, such as created_at, it would also appear in this list:

 >> project.created_at = Time.now
=> [current time]
>> project.changed
=> ["name", "created_at"]

If you wanted to know only if a single attribute has changed, there’s the *_changed? methods for all attributes on the model. For instance, if you wanted to know if the name attribute has changed, then you would call name_changed?:

 >> project.name_changed?
=> true

Of course this method will return true, because you changed your name attribute previously. In addition to this, you’re even able to see what the value was before the change using the *_was method:

 >> project.name_was
=> nil

This time, name was nil before you set it and so nil is returned. If it had a value before you created this object, it would return that instead. Finally, you can even get back an array containing the before and after values of the attribute by using the *_change method:

 >> project.name_change
=> [nil, "Ticketee"]

Now, what would these methods be used for? Well, any number of things, really. You could use it for only running a certain callback if a specific field had been changed, or you could use the *_change methods to log the changes to attributes in another table if you wish.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.141.115