Páginas

Showing posts with label Ruby. Show all posts
Showing posts with label Ruby. Show all posts

Saturday, October 1, 2011

Properly show errors with Paperclip and Formtastic

Paperclip has for while now decided that each of its files should have 5 arrays of errors, one for each field it stores in the database (file_name, file_size, content_type and updated_at) and one for the actual file object.

If you are using it alongside with formtastic (or even if you are not), this can be the cause of some problems when trying to show those errors to the client. Therefore, I created a little method to concatenate all these arrays into one.

def properly_show_errors(record)
  @record=record
  if @record.errors.any?
    flash[:error] = []

    @record.errors.each do |attribute,msg|
      if attribute =~ /.+_content_type/ || attribute =~ /.+_content_file_size/
        proper_attribute = attribute.split('_')[0]
        @record.errors.delete_all_at proper_attribute
        @record.errors.add proper_attribute,msg
      end
    end
  end
end  


This takes a record and merges the content_type and content_file_size errors into its own error array. The message can be internationalized in the model to whom the file concerns, as such:

validates_attachment_size :image, :less_than => 1.megabytes, :message => I18n::t('flash.image_size_1mb')

The record is passed on as an instance variable of the caller class. In my use case this caller class is an action in the controller, for instance when creating a page:

@template.properly_show_errors(page)
flash.now[:error] = t("flash.page_not_updated", :name => page.title) 

This will render the page creation form with the error on the flash div, and the record instance variable with page and its errors. The two things of note in this piece of code are the way of using helpers in a controller with the @template variable, and the flash.now that will make the flash message last only one redirect.

Note: This 'bug' has been fixed in formtastic version 1.2.0 and above, but this is still pertinent for those using a prior version


Monday, July 18, 2011

Project Euler

Project Euler is a website that proposes various problems, related to programming and/or math. You can solve the problem anyway you like, as it only asks for the result. If you get it right, you can then check out how others have done it or, in some cases, have an explanation from the guys at project euler.

It is very addictive and fun, I highly recommend it!

I've been solving the problems entirely in Ruby, and committing my code to github. Check out my solutions, and don't hesitate to let me know if you find a better one, or even a bug. ;)

Saturday, February 5, 2011

Parsing strings from the datepicker

In my previous post I explained how to create a datepicker with dynamic internationalization. There is one catch though, in different languages the representation of the date can be different, for example, February 5th can be 2/5/2011 in the USA and 5/2/2011 in Portugal. The rails default is the first.

This means that you'll have to take this into account when using the string that you get from the form where you are using the datepicker. You have two options, change the way the dates are displayed, by altering all the languages javascripts, or parse the string as it gets to the controller.

The latter can be achieved with this piece of code:


todo.due_date = DateTime.strptime(params[:date],"%d/%m/%Y").to_time


Note that I've chosen that format for the string, but it can be any format according to the ruby's Time class.

There is one other problem though, the user can, maliciously or by distraction, insert an invalid date. We can strengthen our code to prevent this, by catching the exception thrown.


begin
  todo.due_date = DateTime.strptime(params[:date],"%d/%m/%Y").to_time
rescue ArgumentError
  flash[:error] = t("flash.invalid_date")
  redirect_to somewhere_in_the_app_path
  return
end


So, if the exception occurs, we set the flash error message (using the translation helper), redirect to the appropriate path, and then return, so that it does not complain of having multiple render or redirect calls.

Wednesday, January 5, 2011

Manipulating nested hashes in Ruby

Lately I needed to work with some nested hashes in ruby. By nested hashes I mean hashes with hashes (or any other type, actually) in them, something like this:

nested_hash = {"first_key"=>{"second_key"=>"value"},"third_key"=>12}

That was when I found out that there are no actual methods to do this, and therefore I had to come up with my own.

If you want to get all the values in a nested hash, you can do this:

def get_all_values_nested(nested_hash={})
  nested_hash.each_pair do |k,v|
    case v
      when String, Fixnum then @all_values << v
      when Hash then get_all_values_nested(v)
      else raise ArgumentError, "Unhandled type #{v.class}"
    end
  end

  return @all_values
end

Obviously, you could just run trough all the pairs of key/value, but this would only work if there were no nested hashes. If there are, you need to recursively call the method each time you find a hash.

That's why we need the case statement, in order to differenciate between values and other hashes (or Arrays...). If it is a hash, you just give the value to the function and do a recursive search, that stops when it hits a value, adding this value to the final array. In this example I've considered to be values, String and Fixnum, if any other type happens to be present, an exception will be raised.

Note that the all_values variable is an instance variable. It cannot be a local variable, because it's values are going to be used in all the recursive calls. Another way of doing it would be by passing the variable to the method each time and update it at each return. I find it simpler and prettier like this.

In the previous example you'll get an array with all of the values, and that's it. You may, however, want to now what was the path travelled to get to the value. It is actually not that hard to implement, by changing the code just a little bit.

def get_all_values_nested(nested_hash={})
  nested_hash.each_pair do |k,v|
    @path << k
    case v
      when String, Fixnum then
        @all_values.merge!({"#{@path.join(".")}" => "#{v}"})
        @path.pop
      when Hash then get_all_values_nested(v)
      else raise ArgumentError, "Unhandled type #{v.class}"
    end
  end
  @path.pop

  return @all_values
end

There are two main differences in this code, the first is that there is a new instance variable, called path, that's an array, with all the keys that had to be "visited", to get to the value. The last key to be visited is the last key in the array, and is poped each time a value is found, or when all the keys of a certain hash are exhausted.

Imagine you have the hash first presented as an example, the evolution of the path array would be:

["first_key"] , ["first_key","second_key"] - Here, the value is found, and therefore a pop occurs, leaving the array with the previous state, after saving the path in the all_values hash:

["first_key"] - first_key does not have any more keys, so another pop happens, and so on:

[] , ["third_key"] , []

The other difference is that an hash is returned instead of an array. The hash has the following format (using the previous example):

{"first_key.second_key"=>"value" , "third_key"=>12}

So, now you can get the values, and know where they came from, the next thing you probably will want to do is change them and update the nested hash.

def set_value_from_path(nested_hash,path_to_value,newValue)
  path_array = path_to_value.split "."
  last_key = path_array.pop
  hash = create_hash_with_path(path_array,{last_key=>newValue})
  self.merge!(nested_hash,hash)
end

This method receives the path to the value in the format used in the get, and the new value to be inserted. It first transforms the string of the path into an array as the ones we've used before, then it makes the array and the value into a hash, using an auxiliary method, create_hash_with_path. It is a very simple method that gets a path array and a simple hash with the last key before the value, and the value.

def create_hash_with_path(path_array,hash)
  newHash = Hash.new
  tempHash = Hash.new
  flag = 1
  path_array.reverse.each do |value|
    if flag == 1
      tempHash = hash
      flag = 0
    else
      tempHash = newHash
    end
    newHash = {value => tempHash}
  end

  return newHash
end

Afterwards it merges the nested hash (that is the one from the first example), and the newly created hash. The only problem is that you cannot use the merge methods from the Hash class, because it does not work for nested hashes. You can write a simple method that does just that.

def merge!(merge1,merge2)
  case merge1
    when String,Fixnum then merge2
    else merge1.merge!(merge2) {|key,old_value,new_value| self.merge!(old_value,new_value)} if merge1 && merge2
  end
end

It just redefines the Hash's class merge! to be recursive, and to stop when it reaches a value (String, Fixnum), and to using the value in merge2, that is the new value.

So, there you go. Now you have a whole new arsenal of methods to deal with nested hashes. Have in mind that you can chage this code to be compatible with other kinds of values, like Symbols and/or to accept other containers as an Array.