1
\$\begingroup\$

I needed to get the minimum and maximum date values from a query using the Sequel ORM in Ruby from my database. Sequel has a range method that returns a Range value. I'm having it return values from a timestamp field, so they're coming back as a range of Time objects.

In order to split it into two variables using parallel assignment, I figured this was about as concise and succinct as I could get:

template_min_datestamp, template_max_datestamp = [Template.range(:created_at)].map{ |r| [r.min, r.max] }

I considered using:

template_min_datestamp, template_max_datestamp = Template.min(:created_at), Template.max(:created_at)

but that'd cause two hits to the database, which just makes my eye twitch.

Simplifying it for the innocent, and factoring out the ORM calls, it'd look something like this in regular Ruby:

t1 = Time.parse('Jan 1, 2013 12:00:00 -700')
t2 = Time.parse('Jan 31, 2013 12:00:00 -700')
template_min_datestamp, template_max_datestamp = [t1..t2].map{ |r| [r.min, r.max] }

It seems like there'd be a more direct way to break apart a range into two elements on one line using parallel assignment, but it's escaping me right now.

Anyone got some enlightenment to share?

\$\endgroup\$
  • 1
    \$\begingroup\$ template_min_datestamp, template_max_datestamp = [t1..t2].map{ |r| [r.min, r.max] }[0] this works, but I'm not sure if it is what you want. \$\endgroup\$ – Yuriy Golobokov Mar 23 '13 at 5:07
  • \$\begingroup\$ @YuriyGolobokov: yup, that's also what I understood. But then again, although map + first works, it seems a very non-declarative workaround of the abstraction as/into/chain/... \$\endgroup\$ – tokland Mar 25 '13 at 9:47
1
\$\begingroup\$

Update

@steenslag points out the minmax builtin. No monkey patching necessary.

template_min_datestamp, template_max_datestamp = Template.range(:created_at).minmax
\$\endgroup\$
  • 2
    \$\begingroup\$ (2..6).minmax #=>[2, 6] is plain Ruby. \$\endgroup\$ – steenslag Apr 1 '13 at 22:47
0
\$\begingroup\$

If I understood your question correctly, you need a let-abstraction, for example Object#as (or whatever name you like for it, it seems to have dozens). I'd write:

# this goes in your core_ext module.
class Object
  def as
    yield self
  end
end

t1 = Time.parse('Jan 1, 2013 12:00:00 -700')
t2 = Time.parse('Jan 31, 2013 12:00:00 -700')
min_datestamp, max_datestamp = (t1..t2).as { |r| [r.min, r.max] }
\$\endgroup\$
  • \$\begingroup\$ Well, yeah, but that's taken one line of code to seven and hasn't really simplified anything, it's just blurred what is happening. I'm going to go poke at the * operator with .. and see if there's some undiscovered magic there. \$\endgroup\$ – the Tin Man Mar 22 '13 at 16:02
  • \$\begingroup\$ Object#as is a generic method, an abstraction that you won't write there. As I see it this effectively reduces the code to one quite descriptive line: min_datestamp, max_datestamp = (t1..t2).as { |r| [r.min, r.max] }. If vanilla Ruby does not provide an abstraction you need, you write it. "blurred what is happening". This is a archetypical let-block used in lots of languages (and used by many Ruby developers), nothing fancy. Or maybe I didn't understand your question. \$\endgroup\$ – tokland Mar 22 '13 at 16:06
  • \$\begingroup\$ I don't think we're on the same page. I edited the question. Hopefully it'll be more clear. I'm trying to avoid making dual DB requests, and make use of the min and max ends of the range that's returned, without resorting to embedding the range inside [] and using map. I can't decide if that's a skanky hack, or a brilliant use of map. \$\endgroup\$ – the Tin Man Mar 22 '13 at 16:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.