“...I've been working since 2008 with Ruby / Ruby on Rails, love a bit of Elixir / Phoenix and learning Rust. I also poke through other people's code and make PRs for OpenSource Ruby projects that sometimes make it. Currently working for InPay who are based in Denmark...”

Rob Lacey
Senior Software Engineer, UK

ActiveRecord::Associations::CollectionProxy#many?

Nice…

class Person < ActiveRecord::Base
  has_many :pets
end

person.pets.count # => 1
person.pets.many? # => false

person.pets << Pet.new(name: 'Snoopy')
person.pets.count # => 2
person.pets.many? # => true

Multiline Ruby String without interpolation

Whilst trying to clean up old blog posts. I thought I’d just re-assign the whole post on the console. However, the content of the post had code examples and these examples were being interpolated. This makes sense but isn’t what I wanted. These are all (nearly) equivalent other than the new lines.

s =<<-STR
#{Time.now}
STR
# => "2017-01-17 06:43:48 -0500" 

s = %(
#{Time.now}
)
# => "\n2017-01-17 06:43:48 -0500\n"

s = %Q(
#{Time.now}
)
# => "\n2017-01-17 06:43:48 -0500\n"

But what I really want it multi-line string assignment without interpolation.

s = %q(
#{Time.now}
)
# => "\n\#{Time.now}\n"

And without the new lines.

s = %q(
#{Time.now}
).lstrip.chop
# => "\#{Time.now}"

Instagram Subscriptions

Instagram Subscriptions

Loading development environment (Rails 5.0.1)
2.3.0 :001 > puts JSON.parse(Infectious::Instagram.subscribe('http://robl.me/instagram/subscriptions/callback','dave').body).to_yaml
ETHON: Libcurl initialized
ETHON: performed EASY effective_url=https://api.instagram.com/v1/subscriptions response_code=200 return_code=ok total_time=1.394347
---
meta:
  code: 200
data:
  object: user
  object_id: 
  aspect: media
  subscription_id: 0
  callback_url: http://robl.me/instagram/subscriptions/callback
  type: subscription
  id: 0

Method#call

Not sure what to make of this at all.

class Cat
  def call(*args)
    (args).join(' ^O^ ')
  end
end

Cat.new.(:samson, :smeagol, :gimmick)

Rails 4, ActiveRecord::Base, MySQL and DISTINCT

Interesting, ActiveRecord joins issues today. Recently upgraded to Rails 4.0 and working on clearing odd occasional bugs.

Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DISTINCT grapes.id, grapes.* FROM `grapes` INNER JOIN `tickets` ON `z' at line 1: SELECT `pledges`.`id` AS t0_r0, ...., DISTINCT grapes.id, grapes.* FROM `grapes` INNER JOIN `tickets` ....

We specifically need a DISTINCT in here so that we don’t end up with duplicate rows. However, overwriting the select for *eager_load*ed statements isn’t going to work, in fact it appears to just append our select causing the above error. So…

-        scope = scope.joins(:tickets).select('DISTINCT grapes.id, grapes.*')
+        scope = scope.joins(:tickets).distinct('grapes.id')

Don’t use .select(‘DISTINCT … when eager_loading is likely to kick in. You’ll end up with something like.

SELECT `grapes`.`id` AS t0_r0, ..... DISTINCT(grapes.id), grapes.* FROM grapes;

Which will break, since you can’t have two DISTINCT in a SELECT.

Do your columns contain Unicode Characters?

Want to work out if any of your columns contain Unicode Characters?

Something.where('LENGTH(data) != CHAR_LENGTH(data)').first

cannot remove 'v3.0': Directory not empty”

But it is empty???

rails@snarf:~$ ls -la /var/www/robl.me/releases/20160323235726/tmp/cache/assets/sprockets/v3.0
total 40
drwxrwxr-x 2 rails rails 36864 Dec 16 15:11 .
drwxrwxr-x 3 rails rails  4096 Mar 23  2016 ..
rails@snarf:~$ rmdir /var/www/robl.me/releases/20160323235726/tmp/cache/assets/sprockets/v3.0
rmdir: failed to remove ‘/var/www/robl.me/releases/20160323235726/tmp/cache/assets/sprockets/v3.0’: Directory not empty

ActiveSupport Date and Time formats

Just went hunting for Date and Time formats, found this.

activesupport-5.0.0.beta3/lib/active_support/locale/en.yml

en:
  date:
    formats:
      # Use the strftime parameters for formats.
      # When no format has been given, it uses default.
      # You can provide other formats here if you like!
      default: "%Y-%m-%d"
      short: "%b %d"
      long: "%B %d, %Y"

    day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
    abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]

    # Don't forget the nil at the beginning; there's no such thing as a 0th month
    month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
    abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
    # Used in date_select and datetime_select.
    order:
      - year
      - month
      - day

  time:
    formats:
      default: "%a, %d %b %Y %H:%M:%S %z"
      short: "%d %b %H:%M"
      long: "%B %d, %Y %H:%M"
    am: "am"
    pm: "pm"

# Used in array.to_sentence.
  support:
    array:
      words_connector: ", "
      two_words_connector: " and "
      last_word_connector: ", and "
  number:
    # Used in NumberHelper.number_to_delimited()
    # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
    format:
      # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
      separator: "."
      # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
      delimiter: ","
      # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
      precision: 3
      # If set to true, precision will mean the number of significant digits instead
      # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
      significant: false
      # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
      strip_insignificant_zeros: false

    # Used in NumberHelper.number_to_currency()
    currency:
      format:
        # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
        format: "%u%n"
        unit: "$"
        # These five are to override number.format and are optional
        separator: "."
        delimiter: ","
        precision: 2
        significant: false
        strip_insignificant_zeros: false

    # Used in NumberHelper.number_to_percentage()
    percentage:
      format:
        # These five are to override number.format and are optional
        # separator:
        delimiter: ""
        # precision:
        # significant: false
        # strip_insignificant_zeros: false
        format: "%n%"

    # Used in NumberHelper.number_to_rounded()
    precision:
      format:
        # These five are to override number.format and are optional
        # separator:
        delimiter: ""
        # precision:
        # significant: false
        # strip_insignificant_zeros: false

    # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()
    human:
      format:
        # These five are to override number.format and are optional
        # separator:
        delimiter: ""
        precision: 3
        significant: true
        strip_insignificant_zeros: true
      # Used in number_to_human_size()
      storage_units:
        # Storage units output formatting.
        # %u is the storage unit, %n is the number (default: 2 MB)
        format: "%n %u"
        units:
          byte:
            one:   "Byte"
            other: "Bytes"
          kb: "KB"
          mb: "MB"
          gb: "GB"
          tb: "TB"
          pb: "PB"
          eb: "EB"
      # Used in NumberHelper.number_to_human()
      decimal_units:
        format: "%n %u"
        # Decimal units output formatting
        # By default we will only quantify some of the exponents
        # but the commented ones might be defined or overridden
        # by the user.
        units:
          # femto: Quadrillionth
          # pico: Trillionth
          # nano: Billionth
          # micro: Millionth
          # mili: Thousandth
          # centi: Hundredth
          # deci: Tenth
          unit: ""
          # ten:
          #   one: Ten
          #   other: Tens
          # hundred: Hundred
          thousand: Thousand
          million: Million
          billion: Billion
          trillion: Trillion
          quadrillion: Quadrillion

MySQL, Updates with Joins and clearing annoying locks

mysql> UPDATE logs LEFT JOIN ip_ranges ON logs.ip_int BETWEEN ip_ranges.start_ip AND ip_ranges.end_ip SET logs.country = ip_ranges.country_code WHERE logs.country IS NULL;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> show open tables where in_use>0;
+-----------------------------------+---------------+--------+-------------+
| Database                          | Table         | In_use | Name_locked |
+-----------------------------------+---------------+--------+-------------+
| production | logs |      2 |           0 |
| production | ip_ranges     |      2 |           0 |
+-----------------------------------+---------------+--------+-------------+
2 rows in set (0.00 sec)

mysql> show processlist;
+----+------+-----------+-----------------------------------+---------+------+--------------+------------------------------------------------------------------------------------------------------+
| Id | User | Host      | db                                | Command | Time | State        | Info                                                                                                 |
+----+------+-----------+-----------------------------------+---------+------+--------------+------------------------------------------------------------------------------------------------------+
|  1 | root | localhost |production | Sleep   |   14 |              | NULL                                                                                                 |
|  4 | root | localhost | production | Query   | 3857 | Sending data | SELECT COUNT(*) FROM `download_logs` JOIN ip_ranges ON ip_int BETWEEN ip_ranges.start_ip AND ip_rang |
|  5 | root | localhost | production | Query   | 3563 | Sending data | UPDATE `logs` JOIN ip_ranges ON ip_int BETWEEN ip_ranges.start_ip AND ip_ranges.end_ip SET  |
|  8 | root | localhost | production | Query   |    0 | init         | show processlist                                                                                     |
+----+------+-----------+-----------------------------------+---------+------+--------------+------------------------------------------------------------------------------------------------------+
4 rows in set (0.00 sec)

mysql> kill 4;
Query OK, 0 rows affected (0.00 sec)

mysql> show processlist;
+----+------+-----------+-----------------------------------+---------+------+--------------+------------------------------------------------------------------------------------------------------+
| Id | User | Host      | db                                | Command | Time | State        | Info                                                                                                 |
+----+------+-----------+-----------------------------------+---------+------+--------------+------------------------------------------------------------------------------------------------------+
|  1 | root | localhost | production | Sleep   |   46 |              | NULL                                                                                                 |
|  5 | root | localhost | production | Query   | 3595 | Sending data | UPDATE `download_logs` JOIN ip_ranges ON ip_int BETWEEN ip_ranges.start_ip AND ip_ranges.end_ip SET  |
|  8 | root | localhost | production | Query   |    0 | init         | show processlist                                                                                     |
+----+------+-----------+-----------------------------------+---------+------+--------------+------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

mysql> kill 5;
Query OK, 0 rows affected (0.00 sec)

mysql> show processlist;
+----+------+-----------+-----------------------------------+---------+------+-------+------------------+
| Id | User | Host      | db                                | Command | Time | State | Info             |
+----+------+-----------+-----------------------------------+---------+------+-------+------------------+
|  1 | root | localhost | production | Sleep   |   59 |       | NULL             |
|  8 | root | localhost | production | Query   |    0 | init  | show processlist |
+----+------+-----------+-----------------------------------+---------+------+-------+------------------+
2 rows in set (0.00 sec)

mysql> kill 1;
Query OK, 0 rows affected (0.00 sec)

mysql> show processlist;
+----+------+-----------+-----------------------------------+---------+------+-------+------------------+
| Id | User | Host      | db                                | Command | Time | State | Info             |
+----+------+-----------+-----------------------------------+---------+------+-------+------------------+
|  8 | root | localhost | production | Query   |    0 | init  | show processlist |
+----+------+-----------+-----------------------------------+---------+------+-------+------------------+
1 row in set (0.00 sec)

mysql> UPDATE logs LEFT JOIN ip_ranges ON logs.ip_int BETWEEN ip_ranges.start_ip AND ip_ranges.end_ip SET logs.country = ip_ranges.country_code WHERE logs.country IS NULL;

Installing EventMachine on Mac OSX

Robs-iMac:pledge_core roblacey$ gem install eventmachine -v 1.0.7
Building native extensions.  This could take a while...
ERROR:  Error installing eventmachine:
	ERROR: Failed to build gem native extension.

    /Users/roblacey/.rvm/rubies/ruby-2.2.2/bin/ruby -r ./siteconf20161214-75988-ryo2ti.rb extconf.rb
checking for rb_trap_immediate in ruby.h,rubysig.h... no
checking for rb_thread_blocking_region()... no
checking for ruby/thread.h... yes
checking for rb_thread_call_without_gvl() in ruby/thread.h... yes
checking for inotify_init() in sys/inotify.h... no
checking for __NR_inotify_init in sys/syscall.h... no
checking for writev() in sys/uio.h... yes
checking for rb_thread_fd_select()... yes
checking for rb_fdset_t in ruby/intern.h... yes
checking for rb_wait_for_single_fd()... yes
checking for rb_enable_interrupt()... no
checking for rb_time_new()... yes
checking for sys/event.h... yes
checking for sys/queue.h... yes
checking for clock_gettime()... yes
checking for CLOCK_MONOTONIC_RAW in time.h... yes
checking for CLOCK_MONOTONIC in time.h... yes
creating Makefile

make "DESTDIR=" clean

make "DESTDIR="
compiling binder.cpp
In file included from binder.cpp:20:
./project.h:116:10: fatal error: 'openssl/ssl.h' file not found
#include <openssl/ssl.h>
         ^
1 error generated.
make: *** [binder.o] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/roblacey/.rvm/gems/ruby-2.2.2@rails41-trunk/gems/eventmachine-1.0.7 for inspection.
Results logged to /Users/roblacey/.rvm/gems/ruby-2.2.2@rails41-trunk/extensions/x86_64-darwin-14/2.2.0-static/eventmachine-1.0.7/gem_make.out
Robs-iMac:pledge_core roblacey$ cat /Users/roblacey/.rvm/gems/ruby-2.2.2@rails41-trunk/extensions/x86_64-darwin-14/2.2.0-static/eventmachine-1.0.7/gem_make.out
/Users/roblacey/.rvm/rubies/ruby-2.2.2/bin/ruby -r ./siteconf20161214-75988-ryo2ti.rb extconf.rb
checking for rb_trap_immediate in ruby.h,rubysig.h... no
checking for rb_thread_blocking_region()... no
checking for ruby/thread.h... yes
checking for rb_thread_call_without_gvl() in ruby/thread.h... yes
checking for inotify_init() in sys/inotify.h... no
checking for __NR_inotify_init in sys/syscall.h... no
checking for writev() in sys/uio.h... yes
checking for rb_thread_fd_select()... yes
checking for rb_fdset_t in ruby/intern.h... yes
checking for rb_wait_for_single_fd()... yes
checking for rb_enable_interrupt()... no
checking for rb_time_new()... yes
checking for sys/event.h... yes
checking for sys/queue.h... yes
checking for clock_gettime()... yes
checking for CLOCK_MONOTONIC_RAW in time.h... yes
checking for CLOCK_MONOTONIC in time.h... yes
creating Makefile

make "DESTDIR=" clean

make "DESTDIR="
compiling binder.cpp
In file included from binder.cpp:20:
./project.h:116:10: fatal error: 'openssl/ssl.h' file not found
#include <openssl/ssl.h>
         ^
1 error generated.
make: *** [binder.o] Error 1

make failed, exit code 2
Robs-iMac:pledge_core roblacey$ gem install eventmachine -v '1.0.7' -- --with-cppflags=-I/usr/local/opt/openssl/include
Building native extensions with: '--with-cppflags=-I/usr/local/opt/openssl/include'
This could take a while...
Successfully installed eventmachine-1.0.7
Parsing documentation for eventmachine-1.0.7
Installing ri documentation for eventmachine-1.0.7
Done installing documentation for eventmachine after 4 seconds
1 gem installed