Changing link_to to button_to

In one of my rails apps, I send out email to a particular user. But, I can look at the message before it goes out. Then, at the bottom of this preview page is a link to actually send the message. I'm pretty sure that since this action is changing a boolean value on the user along with sending the email, that I should be using a button instead of a link. However, just switching the link_to to button_to won't work because link_to uses a GET request, but button_to uses a POST request. So I need to change the button_to to use GET. The way to do that is like this:

<%= button_to "Send rejection email to #{@applicant.fullname}", 
{:controller => 'applicants', :action => 'send_rejection', :id => @applicant.id}, 
{:method => :get, :disable_with => 'Sending Email'} %>

You must separate into hashes the info for the action and the parameters for the button or it won't work.

Plans for 2012

I've found that a little bit of planning helps me get much more accomplished than just trying to do things willy-nilly. I look back with great satisfaction at my decision to learn Ruby on Rails about three years ago. It took quite a bit of time and I'm not a guru yet, but I can and have created some websites that have been helpful. Since I had a personal day that I needed to use before the end of the year, I've taken it today and will use that to lay out my plans. And because I think that making the plans public will help me stick to them, I'll post them as soon as they're done.

January: This month I know what I'm doing. It's get the floor of the house done so I can move back home. This may also take more than a month. So, it's the highest priority until it gets finished.

As for the rest of the year, that's what I'm going to work on now.

Detaching Rails Processes

In my latest app, I'm emailing reports to people who click on a link. (Yes, I know this should be a button, but I just did it with a link because it was easy. If I have time, I'll change it to a button.) Anyway, I'm attempting to use Process.fork to do this. What I have that works is:

  def email_monthly_report
    monthyear = params[:monthyear]
    process = Process.fork do
      ReportMailer.send_monthly_report(current_user, monthyear).deliver
#      Process.kill("HUP", process)
    end
    Process.detach(process)
    redirect_to monthly_report_projects_path(:month => monthyear), 
                :notice => "Report sent to #{current_user.fullname}<#{current_user.email}>"
  end

From everything that I've read, that Process.kill("HUP", process) line should be required. If I uncomment it, the email gets sent and the page is redirected correctly, but I get an error in the log.

projects_controller.rb:74:in `kill': no implicit conversion from nil to integer (TypeError)

My line process=Process.fork is always sets process to nil. So I apparently need to figure out how fork works. Time to fire up irb.

ruby-1.9.2-p0 > pid = fork do
ruby-1.9.2-p0 >     a = 100
ruby-1.9.2-p0 ?>  end
 => 67773 
ruby-1.9.2-p0 > pid.class
 => Fixnum 
ruby-1.9.2-p0 > pid
 => 67773 
ruby-1.9.2-p0 > Process.kill("HUP",pid)
 => 1 
ruby-1.9.2-p0 > exit

So what am I doing wrong? I really don't want to kill the process, I just want to let it finish whenever it does. If I just take out the Process.kill line, everything does work as expected. Would there be a chance that the process could get stuck and I would need the kill line? I don't know. But since I can't get things to work properly with it, I'm just going to leave it out.

New DNS Registrar

I finally had my fill of all the crap of Godaddy. Actually, that's not quite true. That was a push, but the thing that really got me to switch is just the terrible website that one has to use to do anything with Godaddy. I found a new registrar Badger.com and decided on a whim to switch to them. I got one free credit (so I could transfer one domain for free) from Hacker News and I ended up buying two more for $8 each. I then started the process and was amazed to find that I had my three domains transferred in about 10 mintues. And they added an extra year to my domains. Their website is nice and clean. It's easy to find stuff and they don't try to sell me more crap each time I login. I can't believe I waited this long to switch, but right now, I'm glad I did.

Rails Integration Tests

After unit tests, for me, the important tests are integration tests. This is where I can test logging in and checking authorizations and calculations. They are a bit more involved, but not too difficult once you get the hang of them.

My app authenticates users using ldap. One great advantage I have is that I'm in charge of the ldap system as well. So I just made a couple of ldap users, who were NOT allowed to login to our normal system, and used them to test that logins were working.

Users fixture

# These two accounts have been set up as 
#non login accounts on the server for testing purposes
admin:
  cnetid: admin
  firstname: Admin 
  lastname: User
  role: admin
  rate: 75
  email: [email protected]
  
regular:
  cnetid: regular
  firstname: Regular
  lastname: User
  role: user
  rate: 50
  email: [email protected]

All of my integration tests have a setup and login (private) method.
They look like this:

  def setup
    @admin = users(:admin)
    @regular = users(:regular)
    # Need the without_session_maintenance here or get an error with authlogic
    # "ActionDispatch::ClosedError: Cannot modify cookies because it was closed."
    @admin.save_without_session_maintenance
    @regular.save_without_session_maintenance
    
    @edg1 = projects(:edg1)
    @edg2 = projects(:edg2)
  end

  private
  def login(user, password)
    get "/login"
    post_via_redirect "/user_sessions/create", 
     :user_session => { :cnetid => user, :password => password }
  end

Once I have that setup, I can start writing my tests. Basically, all I do in each test is login with one of my valid users and then run my assertions. As an example, in the following test, I'm logging in as a regular user and then I'm going to try to edit the profile of the admin user. Since I have my authorization rules set so that users (who are not admins) can only edit their own profile, this should not be allowed. If a user does try to edit someone else's profile, they should be redirected to the root_path.

  test "user trying to edit another user profile" do
    login(:regular, 'password')
    get_via_redirect (edit_user_path(id = @admin.id))
    assert_equal root_path, path, "User allowed to edit another user's profile"
  end

I have a bunch of tests that login as my regular user and my admin and try to edit various things.

A more elaborate integration test is one that calculates expenses for a given project in a given month. That test looks like this:

  test "calculate monthly report" do
    login(:admin, 'password')
    get_via_redirect new_timesheet_path
    timesheet1 = Timesheet.new(:user_id => @regular.id, :monthyear => '2011-10-01', 
                :entries_attributes => {"0" => { :project_id => @atlas.id, :hours => 10 }, 
                                        "1" => { :project_id => @cdf.id, :hours => 20 }})
    assert timesheet1.save, "Didn't save first timesheet"
    
    timesheet2 = Timesheet.new(:user_id => @admin.id, :monthyear => '2011-10-01',
                :entries_attributes => {"0" => {:project_id => @cdf.id, :hours => 100 },
                                        "1" => {:project_id => @atlas.id, :hours => 50 }})
    assert timesheet2.save, "Didn't save second timesheet"
    
    # Add another timesheet in a different month to make sure the calculation is
    # only using ones from the month selected
    timesheet3 = Timesheet.new(:user_id => @regular.id, :monthyear => '2011-11-01',
                :entries_attributes => {"0" => {:project_id => @cdf.id, :hours => 1000 },
                                        "1" => {:project_id => @atlas.id, :hours => 1000 }})
    assert timesheet3.save, "Didn't save third timesheet"
    
    get_via_redirect monthly_report_projects_path(:month => '2011-10-01')
    assert_not_nil assigns(:projects), "@projects is nil"

    assert_select "table" do
      assert_select "tr" do
        assert_select "td", "ATLAS", "no ATLAS line"
        assert_select "td", "123456-6200", "no ATLAS account"
        assert_select "td", "$4,250.00", "wrong ATLAS value"
      end
      assert_select "tr" do
        assert_select "td", "CDF", "no CDF line"
        assert_select "td", "234567-6200", "no CDF account"
        assert_select "td", "$8,500.00", "wrong CDF value"
      end
    end
   

In line 2, I login as my admin user. Then I get a new timesheet. Lines 3-19 are just me generating three timesheets. I made sure that I got timesheets from both users and one from a different month.

Line 21 goes to the monthly_reports page and gets the info for the month starting on 2011-10-01. The assertion in line 22 makes sure that @projects is not empty. Next, I look at the html on the page that was rendered. I'm looking for a table and then a row. In lines 26-28, I'm looking for a td with the name of the project (ATLAS), then a td with the project account-subaccount info. Line 28 has the most important assertion, it's checking that the total displayed is equal to the value I calculated that the account should be charged for that month, based on the timesheets I entered earlier. Lines 30-34 just repeat the process with the other account number.

Rails Unit/Functional Tests

In my latest rails project, I am trying to write good tests for everything important. My first tests were all unit tests. These are pretty easy. All they basically do is make sure that the data that's stored in the database is formatted properly. So I have a bunch of tests that make sure that my validations are correct. A sample test looks like this:

  test "save a project without a subaccount" do
    project = Project.new(:name => 'Bit Test', :account => '123432')
    assert !project.save, "Saved a project without a subaccount"
  end

This is pretty basic. What I like about unit tests though are how they make sure a custom validation is working correctly. In this application, I'm tracking costs for a number of projects. Each project has an account and subaccount stored with it. The account-subaccount combination must be unique. So I have a validation in my model like this:

validates_uniqueness_of :account, :scope => [:subaccount]

And I can check this validation with this unit test:

  test "save the same account-subaccount combo twice" do
    project = Project.new(:name => 'NSF', :account => '12345', :subaccount => '1234')
    assert project.save, "Didn't save first account-subaccount combo"
    project2 = Project.new(:name => 'DOE', :account => '12345', :subaccount => '1234')
    assert !project2.save, "Saved an account-subaccount combo twice"
  end

Functional tests check the controller. For this project, everything is behind a login. No one can see anything without logging in. So, the only controller test I can really make is to check that anyone trying to get to a page without logging in is directed to the login page. So I really only have a single functional test for all my controllers.

  test "should not get index" do
    get :index
    assert_response 302, "Didn't get redirected"
    assert_redirected_to login_url, "Didn't go back to login page"
  end

I guess I could write a test for each page (new, edit, show, etc.) behind the login, but I'm satisfied with just checking a single one.

*#$!^&*! Spam

I hadn't been blogging much, so I didn't realize that I was getting slammed with trackback and pingback spam. After my last post, I noticed that there were over 800 comments. I knew this was going to just about all be spam, so I had to figure out what was happening. Since I had already turned off comments, I knew that wasn't how it was coming in. I went to the dashboard and noticed that pingbacks and trackbacks were still on, so I turned them off and thought I was finished. Only after deleting the over 800 comments, did I see them keep coming. So I had to do a little more research to find that the checkboxes only turned off trackbacks and pingbacks for future posts. The earlier posts still had it on, which is why I kept getting the spam. These had to be turned off manually, which was going to be a pain, since I have a number of posts. But then I found a simple mysql command to turn them all off at once.

mysql> use blog;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> update wp_posts set ping_status="closed";
Query OK, 430 rows affected (0.01 sec)
Rows matched: 432  Changed: 430  Warnings: 0

Fixed.

My Turn

When I was a kid, I played in lots of sports. My parents were big believers in organized sports as a way to keep kids out of trouble. Since I tended to find them fun, I, for the most part, happily went along with it. Looking back, I had no idea how much time people freely gave in order for me to have all these opportunities. I didn't appreciate the gift that these teachers and coaches gave me of their time and knowledge. Now, however, I do realize how very generous they were. And, since I'm now an adult, I think that it's my turn. I've made monetary donations in the past to various organizations. But now, it's time for me to give more back. I've been very lucky to have so many opportunities and I think that I now have something that I can offer people. So I need to decide what to do.

I have volunteered coaching kids in the past and was ok at it. The problem with this, is that I have been known to be a complete jerk when I compete. This is why, I basically don't compete anymore. I don't like myself very much when I am absorbed in the idea of "beating" someone. Now, I like to participate in more collaborative things. So, I'm thinking that maybe I should volunteer to teach something. I just met with someone from the Chicago Public Library Foundation tonight and might be able to work something out with them. This seems to be ideal. As soon as I get some time, I'm going to see about coming up with something that I could teach that might interest people.

Sorry for this being so poorly written. It was just something that I was thinking about and wanted to get down before I forgot. This was written in one shot and should probably be edited, but I don't have the time right now.

A Device Attached to the System is Not Functioning

I have a server running openldap and samba, acting like a windows domain controller. I have had no problems with adding windows 7 computers and users until today. One user could not login and this was the error.

A device attached to the system is not functioning.

Looking in the samba log file, I found this:

[2011/12/13 10:46:26.074452,  1] rpc_server/srv_pipe_hnd.c:1602(serverinfo_to_SamInfo_base)
  _netr_LogonSamLogon: user DOMAIN/user has user sid S-1-5-21-1368477355-3167354948-3261350252-4220
   but group sid S-1-5-21-3639540563-330460068-1655887120-513.
  The conflicting domain portions are not supported for NETLOGON calls

Turns out that I had some users who had the wrong sambaSID and sambaPrimaryGroupSID in their ldap account. After fixing this, the user was able to login without any problems.

Apparently, our WinXP with pGina setup doesn't look at the SID because these users were not having any problems logging into the XP computers. It was only when moving to Windows7 that the error showed.