I like ActiveRecord OR-ing

class Creature < ActiveRecord::Base
    scope :is_good_pet, -> {
        where(
            arel_table[:is_cat].eq(true)
            .or(arel_table[:is_dog].eq(true))
            .or(arel_table[:eats_children].eq(false))
        )
    }
end

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;