These are some global methods for dealing with URI encoding and decoding in Ruby, such that it behaves exactly like JavaScript.
{[ .uri-stuff | 1.hljs(=ruby=) ]}
Andrea Ercolino, Software Engineer
These are some global methods for dealing with URI encoding and decoding in Ruby, such that it behaves exactly like JavaScript.
{[ .uri-stuff | 1.hljs(=ruby=) ]}
This is a followup to my previous post How to share code among ActiveRecord models. In this installment I’m going to show some additional generic scopes that I found useful for building up queries. In particular, some of these scopes are twice as generic, because they are defined by wrappers, so that their real name is specific enough to be meaningful.
This scope sets a field to some value. Think about a state field. Instead of writing
where(:state, :accepted)
you can write
with_state(:accepted)
which is a bit easier to read.
Here it is.
# field_scope :state
# --> named_scope :with_state, ...
def field_scope(field, cast = :to_s)
named_scope :"with_#{field}", lambda { |*args|
where(field => (args.size == 1 ? args.first.send(cast) : args.map(&cast)))
}
end
Example:
class Order
field_scope :state
named_scope :not_paid, with_state(:to_be_paid, :paying)
end
Order.not_paid.created_before(Date.yesterday).delete
What I like about this generic scope is that a common word like state becomes a specific term that I can later easily find with a simple and fast text search. So, instead of searching for state, whose matches could include a lot of false positives, I can search for with_state which is specific enough to only match what I’m really looking for. Anyway this is a dull surrogate of what future IDEs (able to understand semantics) will allow, like search for specific usages of common words.
This scope works analogously to the previous one, but for belongs_to relations. Think about a user_id field. Instead of writing
where(:user_id => tom.id)
you can write
belonging_to(tom)
which is a bit easier to read.
It’s almost always possible to guess the name of the field from the class of the object a model is supposed to belong to. If that’s not the case, I’ve provided an as option that accepts the name of the field (without the _id suffix).
model.named_scope :belonging_to, lambda { |obj, options = {}|
options = {
:as => (obj.nil? || obj.is_a?(Integer)) ? 'user' : obj.class.to_s.underscore
}.merge(options || {})
where(:"#{options[:as]}_id" => (obj.respond_to?(:id) ? obj.id : obj))
}
Example:
class Document
belongs_to :user
belongs_to :editor, :class_name => 'User'
end
owned_by_ann = Document.belonging_to(User.with_name('Ann'))
edited_by_me = Document.belonging_to(current_user, :as => :editor)
This scope works analogously to the previous one, but for belongs_to relations that are also polymorphic. In this case, I decided to retain simplicity by means of the with_ prefix. Think about a liked field that has been defined like this
belongs_to :liked, :polymorphic => true
Then it’s a bit tedious (and rude) to explicitly refer to liked_table and liked_id in queries, so this is how to elegantly magic them away.
def sti_member?
column_names.include?(inheritance_column)
end
def sti_root?
return nil unless sti_member?
superclass == ActiveRecord::Base
end
def sti_root
return nil unless sti_member?
klass = self
while ! klass.sti_root?
klass = klass.superclass
end
klass
end
# polymorphic_scope :favorite
# --> named_scope :with_favorite, ...
def polymorphic_scope(field)
named_scope :"with_#{field}", lambda { |obj|
if obj.is_a?(Class)
where(:"#{field}_type" => obj.to_s)
else
where(:"#{field}_type" => (obj.class.sti_root || obj.class).to_s, :"#{field}_id" => obj.id)
end
}
end
All the sti_* methods account for Single Table Inheritance, which is a little tricky to deal with because of how it works behind the scenes.
Example:
class Like
belongs_to :liked, :polymorphic => true
polymorphic_scope :liked
end
class User
has_many :likes
end
favorite_singers = current_user.likes.with_liked(Artist)
favorite_people = current_user.likes.with_liked(User)
favorite_songs = current_user.likes.with_liked(Song)
liked_lady_gaga = current_user.likes.with_liked(Singer.with_name('Lady_Gaga'))
puts "You like Lady Gaga since #{liked_lady_gaga.created_at}" unless liked_lady_gaga.nil?
Note how natural it all becomes, working equally well for classes (also for STI classes, like Artist < User) and objects (like Lady Gaga).
Without a proper method it’s not really possible to calculate the day of the week of a day of the year. A trivial proper method is to look it up into a calendar, be it analogical or digital. Of course you can do that, but it’s not nearly as fun as the method I’m going to describe here.
First and foremost, we know that a year is 365 days, which in turn is 52 weeks plus one extra day. So, if it was easy, we could infer that today one year in the future has the next day of the week from today’s. For example, today is 2013-04-27 and is Saturday, so 2014-04-27 should be Sunday, which is. Analogously it goes the other way around, 2012-04-27 should be Friday, which is. OK, so a proper method could be simply to either add as many days as there are years in the future or subtract as many days as there are years in the past to get from today to the desired date.
Sadly, it’s not that easy, even if we stay into the limits of the Gregorian Calendar. In fact it’s much more difficult because of leap years, happening at a rate of one each four years… approximately. The real succinct formula for finding out if a given year is a leap year is as follows:
A year is a leap year if it's divisible by 400 or it's divisible by 4 but not by 100.
For example, year 2000 was a leap year, but 1900 was not, and 2012 was, but 2013 is not.
The next important bit is to find out how many leap years there are between any two dates. We could easily find it if we already knew how many leap years there are between the 1st day of March of the years of those two dates. In fact,
leap_years(date1, date2) = leap_years(date1, march1st(year1)) + leap_years(march1st(year1), march1st(year2)) + leap_years(march1st(year2), date2)
For example, if date1 was 1800-01-10 and date2 was 2000-10-01, then
leap_years(1800-01-10, 2000-10-01) = leap_years(1800-01-10, 1800-03-01) + leap_years(1800-03-01, 2000-03-01) + leap_years(2000-03-01, 2000-10-01)
and in this case it would be
leap_years(1800-01-10, 2000-10-01) = 0 + x + 0 = x
Both the first and the last term are 0 because (first) 1800-01-10 < 1800-03-01 but 1800 is not a leap year and (last) 2000 is a leap year but 2000-03-01 < 2000-10-01.
To find out how many leap years there are between the 1st day of march of any two years, we can use a little trick, derived from the formula about how to identify a leap year. It is a trick because what follows only works for time intervals and if both limits are after the year 1582, when the Gregorian Calendar was first adopted by catholics.
leap_years(march1st_year1, march1st_year2) = fake_leap_years(year2) - fake_leap_years(year1) fake_leap_years(y) = y:4 - y:100 + y:400, where : stands for the integer division
Following up with our example, we’d have
leap_years(1800-01-10, 2000-10-01) = = leap_years(1800-03-01, 2000-03-01) = = fake_leap_years(2000) - fake_leap_years(1800) = = (500 - 20 + 5) - (450 - 18 + 4) = = 485 - 436 = = 49
If Monday = 1, Tuesday = 2, …, Saturday = 6, Sunday = 0; and today is 2013-04-27 which is a Saturday (T = 6) and we want to know which day of the week (W) was on 2011-08-30; then here is how we can proceed.