Tips & Tricks

    In almost all programming languages, the same problem can be solved in several ways. However, some of them are better, some are worse. For some, you need to write 10 lines of code, for others you can get by with one.

    Improving the code and optimizing it sometimes takes more time than it took to write the first version. You often met a code new to you or an interesting implementation, and you said to yourself: “It turns out that this can be done using standard tools, but I invented a bicycle”? Personally, I do. Therefore, in this article I put together my collection of "bicycles" and told how to get rid of them.

    Array Methods


    A simple example - let's say you have an array of objects of the User class. They have the activated property, which is set to 1 if the user has activated his account. You need to check if all users from the array are activated. We don’t take ActiveRecord into account (there is another way to do it), my goal is to show how to work with arrays.

    The first method, the most primitive:
    1. @users = User.find(:all)
    2. activated_users = 0
    3. foreach user in@users
    4.   activated_users += 1 if user.activated == 1
    5. end
    6.  
    7. # если количество совпадает, значит все юзеры активированы
    8. activated_users == @users.size

    The first thing I did here was to use the each array method instead of a loop:
    1. @users = User.find(:all)
    2. activated_users = 0
    3.  
    4. # лично мне такой способ записи нравится больше,
    5. # чем стандартный для многих языков цикл foreach
    6. @users.eachdo|user|
    7.   activated_users += 1 if user.activated == 1
    8. end
    9.  
    10. # или же в одну строку:
    11. @users.each{|user|  activated_users += 1 if user.activated == 1}
    But even this record later did not suit me. I found a more rational option:
    1. # Метод select позволяет вытащить элементы массива по заданному условию
    2. # И потом мы сравниваем размеры обоих массивов
    3. @users = User.find(:all)
    4. @users.select{|user| user.activated == 1 }.size == @users.size
    However, all the good things have already been invented for us, just read the documentation. Arrays have a wonderful all? Method, which checks to see if all elements satisfy the condition. By the way, there is still an any? Method that returns true if at least one element matches the condition.

    All our code can be written in just one line:
    1. @users.all?{|user| user.activated == 1 }
    2. # или даже так:
    3. @users.all?(&:activated)# при условии что activated принимает значение true/false, т.к. 0 в Ruby считается true

    In addition, there are several more little-known, but useful methods:
    1. # Взять первые 5 элементов
    2. @array.take(5)
    3.  
    4. # Выбрать случайный элемент
    5. @array.choice
    6.  
    7. # Разбросать элементы в случайном порядке
    8. @array.shuffle!
    9.  
    10. # Взять элемент массива по его номеру
    11. # Причем этот метод работает быстрее, чем @array[1]
    12. @array.at(1)

    What is the best way to count the number of elements in an array?


    Count  is the most functional. You can specify the parameters in brackets:
    1. array = [1,1,2,2,3,4,5]
    2. array.count# => 7
    3. array.count(1)# подсчитает количество элементов "1" => 2
    4. array.count{|p|p> 2 }# подсчитает элементы по условию => 3


    Size or  length (they are identical for arrays) is the fastest way, because its only purpose is to simply count the number of all elements.

    Frequently Used ActiveRecord Queries


    Another “bike” I've met is its own implementation of  named scope . For example, you have a Cars table. You often need to go to the base, and pull out cars of a certain color. You can write your own method in the model:
    1. class Car <ActiveRecord::Base
    2.     defself.only_red
    3.         self.find(:all, :conditions=> "color = 'red'")
    4.     end
    5. end
    He is just looking for all the red cars. Turning to him is easy - Car.only_red. But the main drawback is that if you want to sort it all out (or add something like: limit => 5), you will either have to modify the method in the model, or use the standard find.

    A very convenient and easy-to-use named_scope comes to the rescue.
    1. class Car <ActiveRecord::Base
    2.     named_scope :red, :conditions=> 'color = "red"'
    3. end

    Use it almost as well - Car.red. But besides this, you can use it together with the find method, for example:
    1. Car.red.find(:all, :limit=> 10, :order=> "id DESC")
    And they can be combined. First, we add a new scope that allows us to include in the request the users who own the machines:
    1.  named_scope :with_users, :include=>:users

    Now Car.red.with_users will create a know-what- query. Conveniently? Undoubtedly.

    Link_to and his friends


    The most usual link_to link has some more interesting analogs.

    The first is link_to_if, which will output the link only if it matches the condition. If it does not match, there will be no link, only text will remain in its place.
    For example, we have the link “Raise karma”. If the user has already voted, it should not be available. Some make a separate style for this purpose, and display the link as plain text. But you can write:
    1. link_to_if(user.voted_for_karma? == false), "Поднять карму", :action=> "add_karma"
    What can be changed here? Of course, (user.voted_for_karma? == false) doesn’t look clear, because you can simply remove the comparison with false using link_to_unless:
    1. link_to_unless user.voted_for_karma?, "Поднять карму", :action=> "add_karma"
    You can make it even cooler - let’s say that the user has already voted.
    1. # link_name тут (не)используется как название оригинальной ссылки
    2. link_to_unless(user.voted_for_karma?, "Поднять карму", :action=> "add_karma"){|link_name| "Вы уже проголосовали" }
    And you can also give another link if the user is not logged in, for example, redirect to registration:
    1. link_to_if(user.logged_in?, "Создать тему", :action=> "add_post")do|link_name|
    2.      # если незалогиненный юзер тыкнет по ссылке, то попадет на страницу логина
    3.      link_to(link_name, :controller=> "users", :action=> "login")
    4. end

    Quite often you can see the replacement of the link with text if the browser is on the current page. This is done using link_to_unless_current:
    1. <ul id="navbar">
    2.   <li><%= link_to_unless_current("Home", { :action => "index" }) %>li>
    3.   <li><%= link_to_unless_current("About Us", { :action => "about" }) %>li>
    4. ul>
    5. <ul id="navbar">
    6.   <li><a href="/controller/index">Homea>li>
    7.   <li>About Usli>
    8. ul>


    Russification of validation


    Another problem that I once encountered is validation and errors. My application was in Russian (of course), and by default errors were issued in the following format: "Title cannot be empty."

    First of all, you need to  install the Russian gem , I really hope that you already know about it. This will localize most standard messages.
    Then in the config / locales folder you need to open (or create, if not) the ru.yml file. Here is an example from my file:
    1. ru:
    2.   activerecord:
    3.     errors:
    4.       full_messages:
    5.         rate:
    6.           exists: "Нельзя оценить одного и того же пользователя дважды"
    7.           number_limit: "Оценка может принмать значения от -2 до 2"
    8.     attributes:
    9.       news:
    10.         title: "Заголовок"
    11.         body: "Текст"
    12.         article: "Статья"
    As you can see, to localize the title field in the news table, just add the above entry to the localization file.

    The full_messages field is used for full error messages. Here's how they fit into the model:
    1. class Rate <ActiveRecord::Base
    2.   validates_uniqueness_of:rate_owner_id, :message=> "rate.exists"
    3.   validates_inclusion_of  :value, :in=>[-2, -1, 1, 2], :message=> "rate.number_limit"
    4. end
    The message parameter takes the localization file, goes to the address activerecord.errors.full_messages, then at the specified in the model, and returns the localized string. Everything is quite simple. If you do not specify message, then the output will contain a standard error phrase, but in my case I replaced it with mine.

    Methods for the controller


    If you need to return data in an action with both an ajax and the old-fashioned way, then you can check it using request.xhr ?, for example:
    1. def search
    2.     @users = User.find(:all, :conditions=> "login LIKE '#{params[:q]}' ", :limit=> 30)
    3.     render:json=>@usersif request.xhr?
    4. end
    Another useful method is verify. Judging by the name, it serves to verify that the correct request came to the controller. This is mainly for protection, and for some methods I highly recommend using it.
    1. class UsersController < ApplicationController
    2.   # Проверяем метод create - он должен принимать только post запросы и указанные параметры.
    3.   # Если что-то не так, то перенаправляем юзера в начало, и добавляем флеш-сообщение об ошибке.
    4.   verify :only=>:create, :method=>:post, :params=>[:login, :email, :name], :redirect_to=>:index, :add_flash=>{:error => 'Неверный запрос. У вас точно все в порядке?'}
    5. end
    By the way, you should not paint the error in as much detail as possible - the main validation should take place in the model. Here we check the parameters, and swear if something is wrong.

    Debugging


    For debugging applications, I use the logger and ruby-debug gem. A logger is an easy and quick way to find out "what the hell is it nil returns for users instead of." It is done simply:
    1. # inspect я вызываю для удобного отображения объектов в консоли
    2. logger.debug "users = #{users.inspect}"
    By the way, you can use not only logger.debug, but also logger.info, logger.warn or even logger.fatal. You can write RAILS_DEFAULT_LOGGER.debug in your own classes, because it will not respond to the phrase logger.debug.

    If you need to look at or change the state of variables, then you can use the debugger. To do this, put a debugger entry, for example:
    1. class UsersController < ApplicationController
    2.  
    3.   def index
    4.     @user = User.find_by_id(params[:id])
    5.     role = @user.role
    6.     debugger
    7.   end
    8. end
    After that, we need to either start the server with the --debugger option, or add
    1. require 'ruby-debug'
    in environment.rb. After that, we update the index page in the browser -, but it will stop and will not load. It's time to look at the console, there we are waiting for a debugger.
    1. (rdb:1) irb
    2. irb(#):001:0> @user
    3. => #
    4. irb(#):001:0> @user.login = 'Admin'
    5. => "Admin">

    First we go to irb, and then look at our variables. In addition to viewing, you can change them and only then let them into the application.

    You can write a whole book on the debugger, here I gave examples of only basic use. For those who want "all at once" - read the links in the paragraph below.

    Conclusion



    Here I will provide some useful links that have helped me, and, I hope, will be useful to you as well.
    • Guides on the rails . In English. A very useful collection of articles.
    • Ruby Toolbox  - a collection of plugins, gems and extensions. For all occasions.
    • Rails API and  Ruby API . Documentation of the language and framework. Although there are many analogues, I liked this site.
    • Ruby Wikibook , in Russian. It is very easy to read. For interest, you can go to the "Networks" section - there is even a description of the trojan!
    • Rubular  is a site for creating and testing regular expressions,

    Also popular now: