My key takeaway from installing the Thinking Sphinx gem for a project, is that Thinking Sphinx is extremely finickety.
The steps seem simple enough on the Thinking Sphinx documentation website, but many errors and hours later, I can attest that it is full of pitfalls for the unwary, or the plain unlucky.
Our initial steps to install Thinking Sphinx for Rails 3.1 and above - which threw a bunch of errors - were to do the following:
1) brew install sphinx
2) brew install mysql
3) added the following to the Gemfile
- gem ‘mysql2’
- gem ‘thinking-sphinx’
Things I learnt from my experience are as follows:
1) You need to have a specific (older) version of MySQL installed (5.5.28)
Initially Sphinx threw its toys out of the pram because it couldn’t find the correct version of MySQL because brew had automatically installed the latest version of MySQL (5.6.10)
We downloaded 5.5.30 from dev.mysql.com. Or alternatively, you can follow the instructions on this gist to install older versions of mysql using Homebrew. This ushered in the next set of fairly obscure errors.
2) When using Thinking Sphinx with a Postgres database (which we are doing for our project), install Sphinx with both the mysql and postgres flags
The next set of errors from our attempt to debug and install TS, were mostly variants of: ‘ERROR: source ‘location_core_0’: unknown type ‘mysql’ ; skipping’
, ‘Can't connect to MySQL server [or client or lib]’
Although the docs say that Sphinx will automatically detect whether you have MySQL of PostgresSQL, that wasn’t the case for us.
And the only combination that worked was brew install sphinx -–mysql -–pgsql
(we had tried all the other permutations before this one finally worked, sort of).
3) Follow the gem version to the letter
After the installation of sphinx, bundling of gems, and running of rake ts:index
and rake ts:start
, we ran our search in rails console, which returned a Thinking Sphinx object #<ThinkingSphinx::Search:0x3fc3d2550958>
, which resulted in the error
undefined method `next_result' for #<Mysql2::Client:0xac86a54>
We eventually found the solution in Thinking Sphinx’s github issues. Our mistake had been to assume that Thinking Sphinx would work with the latest releases of mysql and thinking-sphinx gems, which is why we had not included the specific gem version in our Gemfile.
But Thinking Sphinx was pernickety enough to require the exact version ‘0.3.12b5’ of ‘mysql2’ (yes, down to ‘b5’!).
Resources:
http://pat.github.io/thinking-sphinx/
https://github.com/pat/thinking-sphinx
Source: XKCD
The last couple of weeks at Flatiron School, we have been split into groups to work on group projects. Anthony, Eugene, Jane, and I have been working on Handrai.se, an in-class question-workflow-management app. In the process of deploying our group’s app, we’ve come to realise that what works perfectly in development, doesn’t always work quite so smoothly in production, because real-life users don’t behave the way we assume they do in our seed file and development testing.
And unlike in development, in production, Rails errors are completely cryptic and uninformative:
To get a more informative error message, you would need to log into the server, and into the ‘current’ folder and type in: tail -f log/production.log
But what if you don’t personally encounter the error?
Avi suggested using an ‘Exception Notifier’ gem to get automatically notified each time an error is triggered in production.
1) Read the updated documentation at https://github.com/smartinez87/exception_notification:
Initially, I was really struggling to work with the Exception Notification gem, because I was reading the old, but SEO-optimised documentation at https://github.com/rails/exception_notification.
I really should have realised that such a popular gem (#2 on Ruby-Toolbox with with > 470k downloads) couldn’t really have its last commit dated 3 years ago.
It was Blake, who pointed out that there was a newer, and much more active, repo:
This repo had much clearer instructions.
2) Configuring ActionMailer:
Exception notification’s instructions assumes that you are using sendmail. We decided to go with mailgun.
You can put the following code in the config/environments/production.rb
and/or development.rb
files depending on whether you want notifications to be sent out only in production or also in development:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
3) Protecting your API keys, settings and passwords:
To protect the security of your passwords etc., you can either use the Figaro gem, or do it yourself:
i) Create a .yml
file in the config
folder, e.g. application.yml
ii) Create global variables in the file, but using a colon (:) instead of an equal sign (=), e.g.:
1 2 |
|
iii) Call these variables using ENV['']
, as in the codeblock in step #2 above
iv) Put the .yml
file in your .gitignore
file:
1 2 |
|
Debugging with Exception Notifier
Most of Exception Notifier’s error messages were detailed and informative. But one in particular (and our first error in fact!) stumped us:
What isn’t precompiled? This is as cryptic an error message as “We’re sorry but something went wrong”. We initially thought it was an asset precompilation issue, but that wasn’t the case.
Bob understood the error immediately. It was “nil isn’t precompiled”, and he knew this because “isn’t precompiled” was slightly further indented than the rest of the message, which indicated a blank space, and hence, “nil”. Riiiiggght.
Apparently, this error arose because we forgot to include a conditionality in our views, to not render a user photo if a user didn’t have a photo. So this was quite easily fixed once we realised what the error was.
Resources:
1) Exception Notification by Sebastian Martinez
2) SMTP configuration at my classmate Victoria’s blog
Source: On picking a perfect password by Euroscientist
Since I have a sieve-like memory, I really need to take detailed notes and practice practice in order to remember how to do things. Hence, I am trying to apply what we’ve learned through lectures in my ToDoList ‘practice’ app, especially on areas that I haven’t had a chance to personally code in our current group ‘Hand Raise’ project.
At the end of my previous blog post on my ToDoList app, my next steps were to:
1) Change the navigation links so they are conditional
2) Add authorization for Users to create, edit and delete tasks
But before authorizing users, we first need to authenticate users, and this will be the main body of my post (making it work). The remainder of my post (making it right) is about refactoring, conditionally displaying links and some validation pitfalls that tripped me up.
Making it work - Rails 3 Authentication in 4 parts:
1) Leveraging Rails 3 functionality
Rails 3 includes a has_secure_password class method which simplifies authentication for the beginner and/or the gem-shy.
i) Generate a User model which includes a password_digest string field, which is required to leverage the has_secure_password functionality
ii)Add has_secure_password in the User model
iii) Include (or uncomment) the bcrypt-ruby gem in the Gemfile
2) Creating logins and sessions
The act of logging in is the start of a new user session, which is a virtual model, which means (per my interpretation), that there are specific rules that govern each session, but that there is no need for an actual model, because nothing is persisted into the database.
i) Generate a sessions controller: rails g controller sessions
ii) Create a login form in view. Note that you should use the form_tag helper instead of the form_for helper because there is no sessions model
iii)In the Sessions controller, write a create method, which says:
if a user exists and the password can be authenticated (note that .authenticate
is a method given by has_secure_password
), then set the user’s id as the user_id of the session.
1 2 3 4 5 6 7 8 9 |
|
iv) Logging out would simply entail setting the session’s user_id to nil: session[:user_id] = nil
v) Logging out in views is a bit tricky:
1
|
|
–> even though the session destroy
method does not accept any arguments, the session_path
expects an argument, so just put any argument in
–> need to include method: “delete” (note that method: :destroy
doesn’t work)
3) Allowing the application remember that a user logged in
Create an instance variable to allow the app to remember who the current user is.
i) In Application Controller (so it is set app-wide), create a private method to store the session’s user_id as the current_user, if session user_id exists.
1 2 3 4 |
|
–> add a helper_method
to allow the method to be accessible to all the Views
4) Allowing the application to test if a user is logged in
i) In ApplicationController, create a private method logged_in? or authorize (or anything that is sensibly-named) that tests if a user is logged in.
Either:
1
|
|
Or:
1
|
|
ii) In each relevant controller (in this case, Lists, Tasks, Users), create a before_filter which runs on specific actions with options, e.g.
1
|
|
or:
1
|
|
Making it right - refactoring authentication, conditionally displaying links, validation pitfalls
1) Refactoring authentication
i) Prettifying routes
To change routes from users/new, sessions/new etc. to signup and login, we need to change the routes and then update the views.
Adding the following routes to the routes file…
1 2 3 4 |
|
…enables us to refactor our code in Views from…
1 2 3 |
|
to…
1 2 3 |
|
ii) Further abstracting methods
We can encapsulate the logic of setting session user_ids in private methods in the ApplicationController:
1 2 3 4 5 6 7 |
|
We can then refactor the SessionsController to use these methods.
2) Conditionally displaying links
I wanted to hide the navigation links for a page if a user is already on that page. The Rails UrlHelper from the ActionView::Helpers were very useful in this regard.
Methods such as link_to_if
, link_to_unless
and link_to_unless_current
provide a lot of options for toggling links on and off.
But to completely hide a link, when a user is on a specific page, I used the current_page?
method as follows:
1 2 3 |
|
3) Validation pitfalls
In this set of iterations, I have also incorporated additional front-end and back-end validations that we learned a couple of weeks ago.
One problem I encountered was with password validation, where I initially applied
1
|
|
This broke my ability to assign a user to a task when creating and adding a task to a list. And that took my AGES to debug. Finally, I realized that the user wasn’t saving because the password validation was failing. I got around it by only validating the password on creation:
1
|
|
Another issue I grappled with was the case-sensitivity of emails. I came across :case_sensitive => false
, but some (admittedly dated) blogs have suggested that it slows down applications, so I have downcased the emails when creating a user, and also downcased the email input when creating a session.
1 2 3 4 5 |
|
1 2 3 4 |
|
Resources:
1) ActiveModel at http://api.rubyonrails.org/
2) Authentication from scratch prior to Rails 3: RailsCasts / ASCIIcasts
3) ActionView at http://api.rubyonrails.org/
It was a really interesting week during which we learnt how useful Javascript is in improving the user interface and experience, how JQuery resolves issues around Javascript cross-browser compatibility, and applied Javascript and JQuery in many contexts, including interacting with Twitter, Flickr and Google Maps APIs.
One of the practical applications of Javascript / JQuery was in form validations, which we combined with back-end Rails validation. While back-end validation is used to ensure data integrity, front-end validation is used to provide end-users with timely feedback to create a more positive user-experience.
In designing a simple user sign-up form (as shown at the bottom of this post), my top 3 JQuery APIs to provide appropriate and timely user feedback are the following:
.submit()
.blur()
.keyup()
.submit()
.submit()
provides users with feedback upon submission of the form. Be sure to use &
instead of &&
so that users get all the error messages at once. – There is nothing more annoying than to get drip-fed error messages each time you click ‘submit’.
1 2 3 4 5 6 7 8 9 |
|
.blur()
I would argue that giving users feedback upon submission is too late, because it requires the user to go back to the top and reconsider every input field again, which requires more mental effort than if feedback was given at each input field, before the brain is occupied with considering a new topic
To this end, .blur()
is very useful. .blur()
is triggered when the user leaves a particular form input field, that is, as soon as it appears that a user is ‘done’ with that field.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
In my code, even though I added .blur()
validation for each input field, I also kept my .submit()
validation to prevent the form from being submitted unless all conditions were met.
.keyup()
This provides even more immediate feedback than .blur()
, as the feedback can be provided ‘live’ as each key is pressed and released.
Real-time feedback may not be appropriate for every single field. For example, it might be annoying to get a live commentary as you are trying to pick a username.
However, it can be used to great effect for specific cases. For example, it is mostly commonly used to give real-time feedback on password strength as users are choosing their password.
In my form, I chose to use .keyup()
during password choice (to indicate password strength, even though I am not requiring a ‘strong’ password), as well as for the validation of confirmation emails and passwords.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Finally, I have also used .text()
and .addClass()
extensively.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
I used .text()
to dynamically change the feedback message to the user, indicating whether a password is “weak”, “medium” or “strong”.
I also used .addClass()
to give visual feedback to users though changing the styling of feedback messages (in my case, toggling between ‘green’, ‘amber’ and ‘red’ traffic light colors).
Resulting form with errors displayed:
My github code for this form: https://github.com/ei-lene/validation_app
Two great resources:
api.jquery.com
oscar otero
We began learning Rails last week at the Flatiron School, with lectures and a couple of guided Rails labs / tutorials.
In this post, I will outline the key development stages* of my first ever non-guided , albeit simple, Rails app (a ToDoList app) and my top tricks & workarounds for the current version of the app.
TL;DR:
Top 4 tricks: f.select ; f.hidden_field, to_param, button_to
As I’m sure most fledgling coders will sympathise with, it seems to be that the biggest hurdle when trying to programme an app (with limited or no guidance) is the ‘coder’s block’. The coder’s version of going tharn at the sight of a blank text-editor. What seemed doable (not to say easy) enough in a guided tutorial, suddenly seems impossible – where do I begin? what do I do?
After several weeks of countless panic attacks as part of my course, I am slowly beginning to learn to deal with my coder’s block, through the following mantras:
1) breathe… Keep Calm and Carry On
2) Make It Work, Make It Right, Make It Fast
On the second mantra, my key takeaway was that even great programmers recognise that Rome wasn’t build in a day, so for me, for now, I just need to Make It Work, by any means necessary. Even if I have use brute force** to machete my way through, the first step is to get it working.
.v001
Making it work:
With that in mind, the first major iteration of my ToDoList app was minimalistic to the extreme***. My only aims were:
1) To create lists and show them on the home page
2) To add tasks to lists
Key issues / next steps:
1) The unordered list was clearly a bad idea – users would have to guess and/or remember the number of the list they want to add their task to
2) The lists should be ordered and should be displayed on the ‘Add tasks’ page
.v002
Making it right:
Top Trick #1 – f.select
When discussing the lab in class, I mentioned that I was intending to add radio buttons to the Task new.html.erb
page so users can choose the list to add their task to.
Avi suggested using a drop-down menu instead, and showed briefly walked through the f.select
option in the Rails form_for
helper
Thus, with a short line of code, I significantly improved my user experience and interface:
1
|
|
Making it work:
1) Users should be able to add tasks to a specific list when on that list page
Top Trick #2 – f.hidden_field
My initial intention was to have an “Add task to this list” link which would redirect to a new route along the lines of /lists/:id/tasks/new
.
However, despite my valiant efforts at throwing all sorts of different code permutations and combinations at Rails, the standard new_task_path
could not seem to register the fact that it had just been routed from a specific list id, and store that list id.****
As a workaround, I decided to implement a task form on the list show page (show.html.erb
) with f.hidden_field(:list_id)
, a technique that I learnt in Jumpstart Lab’s blogger tutorial that we were assigned.
And having implemented that, I’ve realised that it works really well, and is more optimal than having to click on a link to an alternate path. Because in this case, each time the user adds a new task to a list, the list page reloads, automatically allowing the user to add another new task to the list.
3) Users should be able to delete and edit lists. As an initial testing step, I implemented the ability for users to:
i) delete the entire list only
ii) update list name and existing task descriptions
Key issues / next steps:
1) Change the routes of list from id (list/:id) to name (list/:name), to make the routes look more elegant
2) Flesh out edit list functionality – users should be able to delete individual tasks in a specific list
.v003
Making it right:
1) Added list routing via permalink
(where permalink = name.downcase.gsub(“ “,”-“
)
Top Trick #3 – to_param
Avi mentioned in class that you could edit the url routing by editing the to_param method in the relevant model.
I must’ve missed out the second part of his words, because I implemented that, then proceeded to struggle as Rails spat out variants of the following error as I diced and spliced my code creatively to try to get it working:
After extensive googling, I found my solution at Railscasts.
I initially used the first method, implementing "#{id}-#{permalink}"
in to_param
and retaining the .find(params[:id])
in the Controller. But the id of the lists would show up in the url which would not correspond to their position based on the number of existing lists (because deleted lists would retain their unique primary_key id), which I found particularly inelegant.
And so I opted for the second method instead:
1 2 3 |
|
1 2 3 |
|
Making it work:
Added functionality to allow users to delete individual tasks in a specific list, even if the ‘Delete this task’ link was very ugly and incongruous next to the compact ‘Update Task’ submit button.
Key issues / next steps:
1) Change the styling of ‘Delete this task’
2) Improve overall styling
.v004
Making it right:
1) Overall styling improved
2) Button generated for ‘Delete Task’
Top Trick #3 – button_to
Before I discovered the delightful button_to
option via googling, I thought the only way to generate buttons in rails was to create a f.submit
within the form_for
helper. And so I tried various ways to implement a method: :delete
and routing to task_path(t)
within the f.submit
.
Eventually, I gave up banging my head on the f.submit
wall and tried a different tack - CSS buttons! But I found CSS buttons very unwieldy and impossible to style to match the ‘Update Task’ button.
button_to
was a life-saver. With one line of elegant code, I managed to generate a lovely Rails-format button:
<%= button_to 'Delete Task', task_path(t), method: :delete %>
Conclusion / Next steps:
Even though my ToDoList app is still very basic for now, it has come a long way from the start, as this post has demonstrated. In fact, if I had tried to tackle even all of the basic functionality and UI in the current version of the app, I wouldn’t have dared to begin the project.
Instead, I put on my blinkers and took one baby step at a time. As my classmate Jane so aptly puts it: “How Do You Eat an Elephant? One Bite at a Time.”
So far, I have only been iterating through the process of making it work and making it right, as I’m not yet at the stage where I have the skills (or the project complexity) to make it fast.
Next steps:
1) Change the navigation links so that they are conditional – e.g. links to Home don’t show up when a user is already on the home page
2) Add authorisation for Users to create, edit and delete lists
Note: Github’s rollback has been invaluable in the creation of this post, namely the commands: git reset –-hard <commit sha id>
& git push -f
* In reality, there were a lot of mini-steps / stages. But in the interest of simplicity, and because I didn’t commit often enough to be able to rollback commits to each mini stage, I am grouping the changes into 4 stages
** The phrase “brute force” is attributable to my classmate Rahul
*** With bare bones styling, because the white background and default serif fonts give me a headache
**** I spoke to Bob about this on Monday, and he told me that this would needed to be done via nested routes, a topic which will covered in future
.equal?
operator in my earlier post on Ruby equalities, and also when I was wading through Ruby Koans’ about_object.rb.
And so, here’s my summary on the questions I had, and the answers that my research yielded:
1) What is an object id?
2) Why is true.object_id != 1?
3) Which objects have fixed object_ids, and what are the practical implications?
1) What is an object id?
An object id is a unique identifier for an object, much like a fingerprint. All objects have an object id. In Ruby, the method
.object_id
returns an object’s unique identifier, which is presented by a number or a string of numbers.
2) Why is true.object_id != 1?
Given that true and false are a fundamental part of most programming languages, I expected the object id for true to be 1, and the object id for false to be 0.
So it surprised me to find that while false.object_id == 0
, true.object_id == 2
, and nil.object_id == 4
. Why do true and nil have such strange object ids?
The reason for that is that the other, perhaps more obvious numbers (like 1 and 2) are taken up by integers due to the pattern for Fixnum (integer) object ids.
As was tested in Ruby Koans’ about_object.rb, in Ruby, the object ids for integers (i) follow the pattern: i * 2 +1
such that:
This pattern also holds true for negative integers:
3) Which objects have fixed object_ids, and what are the practical implications?
As part of my earlier post on Ruby equalities, I tinkered around a lot with .object_id
in irb, researching the application of the .equal?
operator.
Overall, objects seem to fall into 3 categories:
i) those with fixed object ids
ii) those with temporarily fixed object ids
iii) those with changing object ids
Objects with Fixed object ids:
1) Fixnum
As mentioned above, integers have fixed object ids.
2) Symbols
Once created, symbols have immutable object ids, which will remain constant, even if the values have been reassigned.
For example:
In all cases, the object_id of the symbol name is identical
Objects with temporarily fixed object ids:
1) Variables
Once created, variables will have the same object id each time it is called, unless the variable has been reassigned, in which case, the variable object id will change. While the variable retains its assigned value, its object id is temporarily constant:
If a variable is assigned to a Fixnum, the variable will take on that Fixnum’s object id:
This will also apply in the case where a variable is assigned to a symbol:
2) Arrays
Like variables, arrays have a temporarily fixed object id while the contents of the array have not changed:
3) Hashes
Like variables and arrays, hashes have a temporarily fixed object id while the contents of the array have not changed:
Objects with changing object ids:
1) Floats
Floats have new and different object ids, each time they are called:
2) Strings
Strings have new and different object ids, each time they are called:
3) Instances of an object
A new object, once created, behave very much like variables – they have the same object id each time they are called, as long as they have not been modified. Each iteration of the object will result in a temporarily fixed object id:
However, each instance of an object, including clones of another object, have their own unique object id:
Implications / applications:
As far as I know, the primary (most common) application of the above object id properties, are in the usage of symbols rather than strings for naming things or representing data.
Given that symbols have an immutable object id once they are created, whereas strings have a different and individual object id each time they are called, symbols take up less space in the computer’s memory. Hence, we should use symbols instead of strings as hash keys, or attributes – because if the hash key or attribute will be called over and over again, using symbols will significantly increase the performance of the program.
I found Robert Sosinki’s tutorial on Symbols vs. Strings particularly illuminating.
]]>Thinking of learning to code, and wondering if it’s worth the trouble, or if you should go solo or sign up to a course?
I’ve wrestled with these questions before and this post outlines my thought process and why I’m learning to code at the Flatiron School:
global imperative – coding is the new literacy
personal imperative – empowerment rocks, impotence enrages
optional – exponential learning
Avi said on the first day of school that coding is the new literacy. That really resonated with me, because it’s true.
In the not too distant past, before the days of universal education, reading and writing were considered optional luxuries. Now, no one even questions the need for literacy.
When personal computers first came out, they were considered novel gadgets for a small slice of society – the geeks and bandwagoners with spare cash. – Why would a regular person need to use a computer?
Now, everyone uses some form of a personal computer, all the time, whether it be a computer, laptop, tablets, or smartphones. For many young children today, iPads are often more familiar than books.
Technology and software already underpin most of today’s operations, and will only become more ubiquitous, not less so. And so, knowing how to code – to interact with software with more control instead of being a powerless end-user – will soon be imperative.
I’m educating myself for the future. Do you want to be left behind?
Coding is empowering.
The Boy and I had an idea for a potential start-up several years ago, but neither of us code. More than two years, multiple developers / programming language changes and a large chunk of our savings later, the prototype is mostly built, but still not to our envisioned specifications.
We felt impotent, because we had no control of our project - timeline, deliverables, destiny. And that has been a frustrating and enraging experience.
I wanted to regain control of my life and my destiny, and the first step is to learn how to create.
That was what really pushed me to learn code seriously instead of doing it part time, on weekends – I wanted to be empowered, and coding is empowering.
When I first told my friends that I was hopping on a plane from London to New York to learn how to code, most of my friends were like: “Why do you need to go to New York? Why can’t you learn to code at home? It’s really easy to learn from a book or the web. The best way to do it is just to code, and you can do that at home, and it’s free.” I see where they are coming from, and I think that’s definitely a possibility. A lot of really great programmers have learned their craft by themselves. It requires a lot of time, commitment, discipline, and probably a certain type of personality.
Personally, I’ve tried that, and I’ve realized that it doesn’t really work for me. I’m just not disciplined enough – often get distracted by surfing the web, or doing errands and chores.
Even if you prefer working through your problems alone, rather than working in teams, going to a school can still be a great option, because it can really supercharge your learning.
Having passionate instructors who imbue you with enthusiasm, and guide you through programming pitfalls for beginners, and instilling best practices, really make a difference.
Although I’m not a natural ‘group’ person because I learn best by doing, and often like to figure things out on my own – I’ve really enjoyed group learning in my first week at Flatiron School.
Getting different perspectives or solutions to problems from classmates has been enlightening. When I’m stuck on something, instead of spending hours on it, I can ask an instructor or a fellow classmate for help. When I get tired or discouraged, there’s always someone else to buoy me up and debug my code with me. – It’s a very different experience from sitting alone at my desk and banging my head on the wall.
So if you feel like you’re going nowhere fast with learning on your own, try pairing up, or learning in a group, or signing up to a school.
]]>I started on this trail because I’m a pedant about spelling, and got stuck on Ruby Monk’s first Boolean exercise.
Why was the following solution invalid >> name.equal?("Bob")
and this solution valid instead? >> name.eql?("Bob")
This set me off researching Ruby equality operators.
There seems to be four types of equality operators:-
’==’
‘.eql?’
‘.equal?’
’===’
And so it would seem, to misquote George Orwell, that some equalities are more equal than others.
==
Simple (or Value) equality.
==
returns true if both sides of the operator have the same value, regardless of datatype.
Thus:
However, the Ruby ==
operator is not quite as forgiving its Javascript counterpart. It does not convert strings to integers, nor does it recognise upper and lower case versions of the same letter as being of the same value.
Therefore:
.eql?
Value and Type Equality
.eql?
has stricter equality criteria than ==
. It tests for equality of value as well as data type.
Hence:
.equal?
Strict (True or Object) Equality
.equal?
has the strictest test of equality*, and is “the most equal of them all”. It tests for equality of object ID.
As a newbie Rubyist at the Flatiron School, I have very recently learnt that all strings (even if identical) have their own individual object ID each time they are written (called?), whereas symbols have a unique and immutable object ID, which will remain each time the symbol is called, even if the value has been reassigned.
The moral of the story was that we should only use symbols (and never strings) as keys in hashes, since they are ‘cheaper’ than strings in terms of memory and performance (in fact, in researching this, I have discovered that object IDs would be another interesting post for another day, but I digress…)
Revenons à nos moutons** - it would seem that this equality test would be passed in fairly limited cases - primarily when symbols or integers are tested against themselves***:
===
Case Equality (or “Subset equality” or “False Equality”)
Okay, so I’ve completely made up the equality names in the parenthesis for this. For that matter, all the equality names for all the other operators are also kind of ‘made-up’ by me, but in those cases, I think they are fairly representative of what those equality operators test for.
===
is generally known everywhere, including in the Ruby documentation, as “Case equality”.
I thought that Techbot and TutorialsPoint gave the best illustrations of the Case Equality usage of ===
, which I will illustrate briefly below:
While I agree with the above usage (it clearly works!), I see things more simplistically. To me, the ===
operator seems to test if the item on right hand side of the operator is a subset of the left hand side of the operator.
For example:
The ===
operator doesn’t seem to test for subset in the cases of strings, arrays and hashes, but it works with Regex:
Anyway, because I think ===
is testing if one element (object?) is a subset of another, I don’t think that it can be considered an equality test at all, hence my nickname of “False Equality” for it.
jtbandes says on StackOverflow that ===
is incredibly useful for implementations in range, regex and proc (and people seem to compare it to the #== operator), but since all that is WAY over my head, I’ll just take his word for it, and you can check it out there.
After all this research, I’m kind of at peace with Ruby’s misspelling of .eql?
, because I can see that it is necessary to differentiate it from .equal?
But I’m still annoyed with elsif
… >:(
Notes:
* In my rather-uninformed opinion (as I’m an absolute newbie), I am more than happy to be enlightened if I’ve made any mistakes in my blog post.
** A French expression for - to get back to the subject. Check out Laura Lawless’s guide to French expressions.
*** I’m finding Object IDs pretty fascinating as I’ve called .object_id on multiple permutations of data types as part of the research for this blog post. I might write about this piece of geekery fascination another day. Also happy to be enlightened/ corrected if I’ve made any mistakes.
Resources:
http://truffles.me.uk/rubys-equality-operator
http://techbot.me/2011/05/ruby-basics-equality-operators-ruby/
http://www.tutorialspoint.com/ruby/ruby_operators.htm
http://ruby-doc.org/core-1.9.3/Object.html
http://stackoverflow.com/questions/7156955/whats-the-difference-between-equal-eql-and
]]>
Avi gave a talk on the History of Code at today, and one of the programmers he quoted was Larry Wall, who once said, tongue-in-cheek, that:
The three chief virtues of a programmer are: Laziness, Impatience and Hubris.
On that basis, I definitely qualify! ;)
]]>