Saturday, June 23, 2012

Arrays in Rails are awesome!

This week I was trying to figure out a way to store an array in the database of my Rails app. Apparently it's the easiest thing to pull, and considering how helpful the arrays and hashes in ruby are, this is a feature every Rails developer should be able to use.

Suppose we have the need of saving an array of objects within a model. There is no obvious way to do this, since the database can only accept certain simple types, like integers, strings, booleans, etc. But the trick is way too simple. ActiveRecord allows you to call the `serialize' method on a string or text (preferably text) column, turning it into a YAML parsed array or hash. This means every time the record is saved, the array is parsed into a YAML string and stored that way, and when it is pulled from the database the array is instantly rebuilt from the string. So as far as we should be concerned, the record contains an array, and array-related methods can be called directly on the relevant attribute.

Here's a short example to clarify. Say we have a user-portal, and we want to be able to show the user the last 5 times he or she logged in. We could do this by using 5 separate date columns, but that would be cumbersome and would not serve as a good example. So our migration and user.rb files should look something like the following:

# migration file
...
create_table :users do |t|
  t.text :latest_logins
...

# user.rb
attr_accessible :latest_logins ...
serialize :latest_logins
...

Now all that's left is to take care of the logic, which should be quite simple once you're familiar with the many methods for manipulating arrays and hashes in Ruby. For instance, we could put the following code in the user model for pushing a new log-in date and popping the earliest one.

# user.rb
...
def update_logins
  latest_logins.delete_at(0)
  latest_logins << Time.now
  self.save
end
...


Ruby arrays and hashes are so intuitive, and have some methods that make working with them efficient and simple.

Please let me know if this post helped you, or if you have a issue or question you wish discuss.

Thursday, June 14, 2012

First Fire Revised

It didn't take too long for me to find a better way to solve the issue from last week. I guess that's just the way these things happen...
If you haven't read last week's post, I'll summarize. I was looking for a way to validate a password and it's confirmation, without using a whole other column for the password confirmation field. M solution was to validate manually before updating, and then using the `except' command on the params hash to get just the parameters I want to save.
Turns out there's another option I did not think about, because I wasn't too familiar with the subject. This option involves using the `attr_accessor' method. This method, used in the model, creates a setter and getter for a virtual attribute for the model, which instead of being saved in the database, is only created as an instance variable. This method is mostly used in order to simplify complex forms, and it will also be very helpful in this scenario. Let's see how.

So here is my form again, just as it was in the previous post:
<%= simple_form_for(@user, ...) %>
...
<%= f.input :password %>
<%= f.input :password_confirmation %>
...
<% end %>

And here is my updated model:

#user.rb
...
attr_accessible :password_confirmation ...
attr_accessor :password_confirmation

validates :password, presence: true, confirmation: true
validates :password_confirmation, presence: true

And that's all there is too it. In fact, if you were to include the password_confirmation as a column in the database, everything would look the same except for that attr_accessor line.
It may be worth to mention that also in the form view the virtual attribute is treated as if it existed in the table, so no special code to add there either, while in the controller there is no need in manual validations or in manipulating the params hash like I did last time.

I must say, I was quite amazed by how simple this solution is, especially since it allows you to keep on using the normal validation methods.

That's it for this week. Next week I promise to fixate on something different :)

Tuesday, June 5, 2012

First Fire

Hi there.
This blog is here mainly in order to share the stuff I learn about RoR while on the job, for others to learn and contribute, for future reference, and for fun.
I am currently working on a pretty big project - creating an alumni web-portal for a relatively large non-profit organization. When I say relatively, I mean for me, and when I say large I mean about 3000 existing graduates, and about 300 more every year or so. I started a month ago, and the launching deadline is in about another month. Thing is, I'm a year-old noob as far as Ruby on Rails, and this is by far the most complex challenge I have ever been faced with in my career. Oh, and the god damn web-designers haven't come through with the final designs yet, so we're a bit off schedule.

I want to share a little neat trick I learned today, which, as far as I'm concerned is a solution to a fairly annoying problem. The thing is, I'm not quite sure it's the best solution out there, so if anyone knows of a better way to do this, let me know!
So let's dig in:

In the sign-up form, the user is asked to enter his password twice, once in the password field, and another in the confirmation field. In the from, the most simple way to do this would be this:

<%= simple_form_for(@user, ...) %>
...
<%= f.input :password %>
<%= f.input :password_confirmation %>
...
<% end %>

(* In my code I use many methods from gems such as simple_form and bootstrap that might be unfamiliar to some, but the methods are pretty straightforward, and besides, you should probably check these gems out!)

The problem with this is that in your standard create or update action, rails will try to pass this password_confirmation attribute to the model, and hit an error.

My initial solution to this was to create a column in the users table called password_confirmation, add the attribute to the attr_accessible method in the model, and then just live with an unnecessary column until the end of  time. That just won't do.

My second solution was quite silly, but it involved creating a dummy model, which would be used only to validate the password confirmation, and thus would never actually be saved. However this was like taking a walkman to a party after you've decided not to take a diskman. An unused model is far worse than an unused column.

My third solution was to somehow omit the password_confirmation from the parameters sent to the model, however this would mean implementing my own confirmation validation before saving. At this point I ran out of ideas, and the internet was giving me too many deprecated solutions. So I went for it, and this is how I did it:

def create
@user.confirm_password
@user= User.new(params[:user].except(:password_confirmation))
...
end
...
def update
@user.confirm_password
if @user.update_attributes(params[:user].except(:password_confirmation))
...
end

I think it's pretty staight forward, but in short, the except method allows you to omit certain entries from the params hash with ease. The confirm_password method is used to do exactly what the confirmation validation does, so it's not ideal, but I prefer that to having unnecessary columns or models...