Welcome to my journaling about my learning Ruby and Rails. Have fun. Caution: a third of the content is personal and off-topic.
This is Day One of me journaling about learning Ruby on Rails. I started learning both Ruby and Rails before Christmas. I picked the fabulous book Simply Rails as my teacher. I stopped reading the book several times and for long enough that I needed to rewind and read the same chapters over and over again. I have to understand the ins and outs in whatever I learn, not because I'm curious, but because it's the only way that any computer stuff will stick into my brain. I have trouble learning about things when I do not understand how they work.
RoR is a great subject to journal about. Ruby is a beautiful language, and Rails is an extremely sexy framework. My long-term relationship with Drupal has nicely prepared me for it. There's an endless list of equivalences between Drupal and Rails.
Colon cleansing journaling is by far more interesting to most people, to me included, but I'm not cleaning my colon these days. Actually, as an aside, I am on Day 2 of a very expensive homeopathic detox regimen (oh that sounds bad I know), and the 3 products I'm using don't contain any laxative or fibers. I can't say for sure that it's doing me any good. I feel as sick as ever. So, I'll report on that as well. Short story: I asked a sales woman at Supermarket Avril “What's your favorite detox product”, and one thing lead to an other.
Ok, back to Ruby and Rails. Here is what I use:I have since bought many other books, well 2. The ugly but I assume useful because everyone swears by it Agile Web Development with Rails, Third Edition. And O'Reilly's Learning Rails, which contains many errors (not typos), so many that I will not bother reporting them to the publisher as it would be too much work. I'm also reading the entertaining Head First Rails on Safari, and it's not as bad as many have reported it's actually very good. I respect how the information is presented, it follows the Head First “your code gets better when you know better” routine: not necessarily starting with the absolute best practices in the world, but moving on to them when and because you understand their usefulness.
This is the second day of me journaling about learning Ruby on Rails, but it's been ten days since I did my first journal entry.
I got hooked on reading Head First Rails on Safari. I'm already on page 307 of 417, not bad hein? Will I ever read a Rails book from cover to cover? I'm currently reading 3 books simultaneously, and that's counting only the books on Rails. Pathetic? Maybe not. I need my panel of experts, I need a second opinion, I am a compulsive looker-for-a-second-opinion. Each book brings something to my multifaceted fest quest. (This time around, the reason I haven't finished a book yet is not because I've lost interest.)
E., who works for Jaded Pixel, asked me today: “How do you like Head First Rails? Anything missing? Anything particularly good?” To which I replied (my book review, verbatim):
“There isn't always a clear separation between Controller and View Logic. For example, when using a partial in a view, local variables 'sent' to the partial are set right in the view by calling on the model, @variable = Model.find..... while this variable should be set in the controller. Also, in the chapter about Ajax, the edits to the webpage using 'page' are done in the controller rather than a separate rjs template. And without javascript there's nothing that's rendered. No 'graceful degradation'.
But all these things are minor compared to what the book gives you. And while I was working out all the examples I improved on them anyway, making them mine, and that was part of the fun. There's a problem with other books in that they give you a clear path... but don't make you 'fudge' around at all, so in the end you can't find your way if you get lost a little bit in the Rails forest. In a HEAD FIRST book, you are forced to fudge around, so in the end, you will always find your way... That's the best analogy I can find, excuse my language.”
Today, I stumbled on an excellent screencast on ActiveRecord. The execution is a little amateurish (low budget), but the content is awesome: ActiveRecord Tutorial by RailsEnvy.com The screencast shows you the SQL generated by playing around with models in a Ruby console. (BTW, until recently, I didn't know you had to pronounce SQL sequel. Unbelievable.) Suffice to say: It's amazing how far naming conventions and a few lines of code that read like English will take you. Oooh I loove Ruby. I was about to say I love Rails, but ActiveRecord is a Ruby class that can be used in other frameworks. Such as Merb. Speaking of Rails and Merb, thinking of the upcoming merge... #railsconf is raging now and I so much wish I was there. I wish I was there rather than here... sitting in my wheelchair, in my living room, slash dining room, slash office, with C. in Switzerland. There does not seem to be many women at the conference. Too many hard-core geeks too. I know what I'm saying: I've looked at video footage taken at the conference. Just too many dicks? Maybe I wouldn't feel comfortable there. “Spread out the dicks a little. Create some lady space.” Ah.
What are you saying, Caroline? That's not true! I've been too many times in situations where I was the only girl, and I've felt very comfortable then. It's quite pleasant really, and feels strangely natural to me. How so? Maybe it comes from studying at Polytechnique and working as an engineer in a department where I was the only girl. No, it goes way back. In my childhood, I was hanging out with guys mostly. My first best friend was a boy... I would, however, feel uncomfortable at said conference because I feel like I don't belong when surrounded by hard-core geeks. Not sure how to call these guys. Without being judgmental. At Polytechnique, I was studying electrical engineering, and during my last semester I took a few classes in Computer Science Engineering and what a shock. I discovered a new male species. The guys in Computer Science were unlike the guys in other Engineering departments. They were withdrawn, intense, not funny, they were touchy, etc. I had a bad start with hard-core programmer geeks. Interesting fact: there was absolutely no girl in that department then. There were a few girls in Electrical Engineering, and there we, girls, felt right at home. No sexism was felt. Ever. But when I paired off with a guy in my Computer Science class — that was my last semester, fourth year, I wasn't a rookie and had very close to 4 as cumulative average, 4 being the highest score — I faced sexism for the 1st time at Polytechnique. The guy insisted to take on the programming part, and that I stay out of it, he wanted me to write the 'paper' (read: paperwork that must be presented with the project, that usually no one wants to write). Whatever, I thought. I have better things to do than to convince you of my capacities, have it your way, this is my last semester, and then I'm outta here.
It's probable I'm being unfair here. Electrical engineering had its share of hard-core geeks, or rather nerds. I confuse the two, I'm still unsure what the difference is (geek versus nerd). Maybe nerds transform into geeks at a later age. Maybe geeks is what nerds proclaim to be. Maybe a geek is One-that-could-have-been-a-nerd-but-was-lucky-enough-to-be-in-a-nurturing-environment-that-values-nerd-activities, and so on and so forth. It's possible that I simply did not make an effort to mix with other students while in my last semester as I was also working full time, night shifts, and was heart-broken. I can't remember any other specific guy from any of these Programming classes, anyway. I only remember impressions, and a memory of a memory of an event.
Where did time go? Last time I added an entry to my Learning Ruby on Rails journal, it was May 6th. Today is October 30th. There may have been 40 days of learning between then and now. I am back into it — is what matters. Teaching myself Ruby on Rails has become a sort of yoga practice in my life. It feels mostly good and places a nice comfy shield between all the bad noise in my life and my easily-troubled head.
I have set each chapter of a book I have yet to finish reading as 'action' in my Action Method. The book is Simply Rails 2. I am plowing along. The grass is green and cut short and clean. Patrick Lenz is a fantastic writer. Thanks to him, the pasture smells great. Actually, I smelled something funny tonight. I got back into wrapping my head around RJS templates.
They are written in Ruby. Like *.html.erb templates, they are stored in the views folder. They have the extension .js.rjs because once the Rails engine is done with them JavaScript is returned to the browser. Ah, I am not explaining well. Good definition found in Foundation Rails 2: “RJS is Rails’s solution for generating JavaScript code using Ruby. [It] is a server-side solution for generating dynamic responses to AJAX requests without having to resort to writing [...] JavaScript. [...] RJS code [...] resides in specially named RJS templates that can be found in the same directories as our traditional HTML views. However, whereas our standard view template would be named something like index.html.erb, the RJS template for the same action would be named index.js.rjs. So you can see it [follows] the same naming convention with the first word [being] the action it’s associated to, the second [word] representing the type of content that we’re returning (js for JavaScript in this instance), and the final [word] representing the rendering engine that is used to parse the template (RJS for this).”
I usually install the vendor plugin jRails to get rid of Prototype and script.aculo.us when I build any new application. There exists a jRails gem now, but I didn't have much success with it.
Not sure whose fault it is, probably that of jRails, but I'm sending the authenticity_token twice with every Ajax request.
I am using the form_remote_tag helper:
<% form_remote_tag :url => story_votes_path(@story) do %> <p><%= submit_tag 'Shove This' %></p> <% end %>
The following HTML is generated:
<form action="/stories/5-google/votes" method="post" onsubmit="jQuery.ajax({ data:jQuery.param(jQuery(this).serializeArray()) + '&authenticity_token=' + encodeURIComponent('zctxjCg1I5w9LTgk7YvvSEsvAFN4Km+FzvsJuU2Jrok='), dataType:'script', type:'post', url:'/stories/5-google/votes'}); return false;"> <div style="margin:0;padding:0"> <input name="authenticity_token" type="hidden" value="zctxjCg1I5w9LTgk7YvvSEsvAFN4Km+FzvsJuU2Jrok=" /></div> <p><input name="commit" type="submit" value="Shove This" /></p></form>
The form already contains a hidden input field that stores the authenticity token (this protects against forgery). jQuery serializes the form, hence serializes that token, but then... it adds it again! That's not right.
data: jQuery.param(jQuery(this).serializeArray()) + '&authenticity_token=' + encodeURIComponent('zctxjCg1I5w9LTgk7YvvSEsvAFN4Km+FzvsJuU2Jrok='), ...
The following code should suffice:
data: jQuery.param(jQuery(this).serializeArray()), ....
Whatever.
Also, RJS requires some getting used to for me. Seems weird to have an Ajax request return JavaScript to execute, rather than JSON, or text data, or XML, or nothing. I usually use callback functions when doing Ajax: when the data is returned, if any, some JavaScript function is run, doing whatever to the page. There's really different shit happening here, although results are the same of course.
text/javascript; charset=utf-8
The JavaScript returned to the browser is a big Try and Catch. If a JavaScript error occurs, an alert box tells you what went wrong.
try { jQuery("#vote-score").html("Score: 112"); jQuery("#vote-score").effect('highlight',{},2000); } catch (e) { alert('RJS error:\n\n' + e.toString()); alert('jQuery(\"#vote-score\").html(\"Score: 112\"); \njQuery(\"#vote-score\").effect(\'highlight\',{},2000);'); throw e }
Eh, this looks a bit convoluted:
jQuery.param(jQuery(this).serializeArray())
Couldn't we just use this — not sure:
jQuery(this).serialize()
I prefer unobtrusive jQuery just because it's more manageable. I also like to write my own JavaScript — or at least be able to edit it. Ryan Bates shows here how to use jQuery without jRails. No helper functions. No JavaScript generated for you. Ryan Bates uses Ajax requests that return JavaScript as does jRails. But the JavaScript that's returned is stored, this time, in a .js.erb template.
The form on the page is submitted asynchronously thanks to some jQuery added to application.js. (Shown below.) Ryan Bates uses a simple jQuery.post call. And he does uses jQuery(this).serialize! I may try to modify my Shovell app (the book project in Simply Rails 2) to use a .js.erb template. What we're comfortable with may not be the better way, though. If I get used to using RJS templates, I may learn to love them. Not sure.
So I modified my Shovell code to use unobtrusive jQuery. It works.
Instead of using the form_remote_tag helper, I am using a regular form_for helper:
<div id="vote-form"> <% form_for [ @story, Vote.new ] do |f| %> <p><%= f.submit 'Shove This' %></p> <% end %> </div>
The following HTML is generated:
<div id="vote-form"> <form action="/stories/4-hell-breaks-loose/votes" class="new_vote" id="new_vote" method="post"> <div style="margin:0;padding:0"> <input name="authenticity_token" type="hidden" value="qA83IKqd05mqmSl8zyigMzmPeK+o6A+m7b683FhN16U=" /> </div> <p><input id="vote_submit" name="commit" type="submit" value="Shove This" /></p> </form> </div>
Not a trace of JavaScript in it.
Both the jQuery library and application.js are included on the page, so I open application.js and add this JavaScript:
// Place your application-specific JavaScript functions and classes here // This file is automatically included by javascript_include_tag :defaults jQuery(function() { jQuery('#new_vote').submit(function() { // $.post(url, data, callback, type) jQuery.post( jQuery(this).attr('action'), jQuery(this).serialize(), null, 'script' ); return false; }); });
Ryan Bates' code would have been — if I am to look at the source code provided with his jQuery screencast:
// Place your application-specific JavaScript functions and classes here // This file is automatically included by javascript_include_tag :defaults jQuery.ajaxSetup({ 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")} }) jQuery.fn.submitWithAjax = function() { this.submit(function() { $.post(this.action, $(this).serialize(), null, "script"); return false; }) return this; }; $(document).ready(function() { $("#new_review").submitWithAjax(); })
I don't need to set my request header to accept “text/javascript”. I ran my app in Safari, IE6, IE7, IE8 and Firefox, and it worked just fine there. Ryan Bates was using jQuery v1.2.6 when he recorded his screencast on Nov 17, 2008 — I'm using jQuery v1.3.2. Maybe that explains it?
I create a create.js.erb template in app/views/votes and in it I place a simple alert with embedded Ruby in it. See what I get:
Of course, my votes controller has been set to be able to respond to both a regular POST request and a remote Ajax one:
class VotesController < ApplicationController def create @story = Story.find(params[:story_id]) @story.votes.create flash[:notice] = 'Thanks.' # The following would work too. # unless request.xml_http_request? # redirect_to @story # end # Rails uses the HTTP Accept header to determine the request type. respond_to do |format| format.html { redirect_to @story } format.js end end end
It was easier than I thought. It took me 3 minutes to figure things out. This demo page on the jQuery UI website was useful.
So, to accomplish exactly the same thing that was previously done by my create.js.rjs template:
# replace the content of #id with this HTML page.replace_html 'vote-score', "Score: #{@story.votes.count}" # apply visual effect :effet_type to the content of #id page.visual_effect :highlight, 'vote-score', :duration => 2
I am now using these chained commands on my #vote-score div in create.js.erb:
jQuery('#vote-score').html("Score: <%= @story.votes.count %>").show('highlight', {}, 2000);
I am writing my own JavaScript, jRails is not writing it for me, but it's jQuery so it's mindnumbingly easy.
The JavaScript that was sent to the browser when using jRails was pretty much the same — except it was wrapped in a Try and Catch construct:
I am so dumb: I should have referred to it when figuring out how to code my effect.
The JavaScript now returned — the one I had to write, that was not generated by Rails — is this one:
If I feel lazy, I guess I can use RJS.
Of course, one can make his jQuery unobtrusive and still use RJS templates. As long as you don't use form_remote_for, form_remote_tag, link_to_remote and periodically_call_remote helpers, but add event handlers to application.js instead, then you're good.