One day, someone mentions that a couple of the birds – the redbreast and hedge-sparrow – are now more commonly known as the robin and dunnock. Not wanting to confuse our users, we decide we should use the modern vernacular.
Instead of going back and changing the data we can create a new list of bird names – updating any names that have changed with their modern equivalents.
As in the previous post, we treat the hash as a proc.
>bird_names.map&bird_names_and_modern_equivalents
The result isn’t quite what we want. We have the new names for the redbreast and the dunnock but all the other birds are now nil.
=>["robin",nil,nil,"dunnock",nil,nil,nil,nil]
We want our list to contain the original names if there are no substitions in our hash.
Just as we can set a default hash value, we can also set a default proc.
Below, if we ask the hash for the value of a key that doesn’t exist, it returns the value of the default proc instead. In this case, it returns the name of the bird we asked for unchanged.
Name.random.then yields the random name to a block wherein we create a new user. Following the example from the previous post, we then assign the newly instantiated name inside the #tap block.
Object#tap yields the object it is called upon to a block but the resulting value is the value of the object (not the value of the block). For example:
>1.tap{|obj|putsobj*2}2=>1
The value 1 is passed to the block where we output the result of obj * 2 (2). However, the value of the expression is the value of the object: 1.
Say we have a User class:
classUserattr_accessor:nameend
To create a new user, we might do something like this:
The return value of the above method is a new user object with its name assigned to the value of @name.
You can see we have to create the temporary variable new_user to hold the value of the new user in order for us to assign its name. We then return the value of the temporary variable.
#tap removes the need for the temporary variable.
defuserUser.new.tap{|obj|obj.name=@name}end
In the above example, we call #tap on the newly instantiated user object. Inside the block, we assign a name to the new user. The resulting value is the newly instantiated user object with its name assigned the value of @name.
In Ruby, classes are open. They can be modified at any time. You can add new methods to existing classes and even re-define methods. Rubyists call this monkey-patching.
In the example below, I’ve added String#word_count to return the number of words in a string.
classStringdefword_countself.split.countendend
"Don't put your blues where your shoes should be.".word_count# => 9
However, this change is global. What happens if someone else defines their own version of String#word_count based on different rules? Say, they might want to count the individual parts of a contraction, or ignore digits and any non-word characters.
Refinements allow us to monkey-patch classes and modules locally.
Here, I’ve created a new module and used Module#refine to add word_count to String.
The refinement isn’t available in the global scope:
"Take my shoes off and throw them in the lake.".word_count# => NoMethodError: undefined method `word_count' for…
To activate the refinment, I need to use using:
usingMyStringUtilities"Take my shoes off and throw them in the lake.".word_count# => 10"A pseudonym to fool him".word_count# => 5
If I activate the refinement within a class or module, the refinement is available until the end of the class or module definition.
Below, the refinement is not available at the top level because it is scoped to MyClass.
moduleMyStringUtilitiesrefineStringdodefword_countself.split.countendendendclassMyClassusingMyStringUtilitiesdefself.number_of_words_in(string)string.word_countendend"Out on the wily, windy moors".word_count#=> NoMethodErrorMyClass.number_of_words_in("Out on the wily, windy moors")#=> 6
Audiobooks tend to take up a lot of disk space so often streaming is the only practical method of delivery. Piotr writes about using AWS to convert MP3s and using Cloudfront to handle the streaming.
Often when I begin a project, I find myself rolling back migrations, dropping the database, renaming tables and columns whilst I figure out the shape of the model. If I need to rename a column, I’ll just update or delete the corresponding migration.
However, it is possible to change a column’s name in a migration.
Say, I have a table tasks and I want to change the name of the title column to name. First I ask Rails to generate the migration:
class RenameTitleToNameInTasks < ActiveRecord::Migration[6.0]
def change
end
end
I then remove change and add the up and down migration like so:
class RenameTitleToNameInTasks < ActiveRecord::Migration[6.0]
def up
rename_column :tasks, :title, :name
end
def down
rename_column :tasks, :name, :title
end
end
rename_column takes three arguments. The first is the table name. The second is the existing column name. The third is the new name.
Above, I have defined the up and down migration. That is, the up migration does the renaming bit when we migrate the database. The down migration runs when we roll back the migration – in this instance, it reverts the column’s name back to the original.
Before I run the migration, I give the schema a quick check.
Of course, five minutes later, I might decide that the new name probably isn’t the best and I can run rails db:rollback. On doing so, I can see that the column name has reverted to title.
A customer can have many green pencils. But, as far as the customer is concerned, these are just pencils. It makes sense for me to call the relationship that.
I first establish that a green pencil belongs to a customer.
I’m telling Rails that a Customer has many pencils, the associated class is GreenPencil and that this relationship is the other side of GreenPencil’s relationship with a customer that I established at the start.
Rails usually sets inverse_of on all associations itself. However, because the class name differs from the association name, we need to specifiy it explicitly.
Rails will infer the inverse relationship if we set foreign_key but I think inverse_of is clearer to the reader.
Sometimes it’s desirable to update one model’s attributes through an associated parent. Say, I might provide a form that asks a user for their name and email address but the user’s name is stored in the users table and their email address is recorded in another.
Rails provides the accepts_nested_attributes_for macro.