How it works...

The code in the recipe defines two methods. They are normal Python methods, having self as their first argument, and can have additional arguments as well. The methods are decorated with decorators from the odoo.api module.

A number of these decorators were initially introduced in Odoo 9.0 to ensure that the conversion of calls is made using the old or traditional API to the new API. As of Odoo 10.0, the old API is no longer supported, but the decorators are a core part of the new API.

When writing a new method, you will generally use @api.multi. This decorator indicates that the method is meant to be executed on a recordset. In such methods, self is a recordset that can refer to an arbitrary number of database records (this includes empty recordsets), and the code will often loop over the records in self to do something on each individual record.

The @api.model decorator is similar, but it's used on methods for which only the model is important, not the contents of the recordset, which is not acted upon by the method. The concept is similar to Python's @classmethod decorator.

Here's an example code snippet calling the change_state() method from the recipe:

# returned_book_ids is a list of book ids to return 
books = self.env['library.book'] 
books.browse(returned_book_ids).change_state('available')

When change_state() is called, self is a (possibly empty) recordset containing records of the library.book model. The body of the change_state() method loops over self to process each book in the recordset. Looping on self looks strange at first, but you will get used to this pattern very quickly.

Inside the loop, change_state() calls is_allowed_transition(). The call is made using the book local variable, but it can be made on any recordset for the library.book model, including, for example, self, since is_allowed_transition() is decorated with @api.model. If the transition is allowed, change_state() assigns the new state to the book by assigning a value to the attribute of the recordset. This is only valid on recordsets of length 1, which is guaranteed to be the case when iterating over self.