Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Relation#pick as short-hand for single-value plucks #31941

Merged
merged 2 commits into from
Feb 9, 2018
Merged

Conversation

dhh
Copy link
Member

@dhh dhh commented Feb 9, 2018

Pick the first value from the named column in the current relation. This is short-hand for relation.limit(1).pluck(column_name).first, and is primarily useful when you have a relation that's already narrowed down to a single row.

Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also more efficient. The value is, again like with pluck, typecast by the column type.

  Person.where(id: 1).pick(:name)
  # SELECT people.name FROM people WHERE id = 1 LIMIT 1
  # => 'David'

@composerinteralia
Copy link
Member

composerinteralia commented Feb 9, 2018

Might we want pick to take multiple arguments like pluck?

  Person.where(id: 1).pick(:name, :email)
  # SELECT people.name, people.email FROM people WHERE id = 1 LIMIT 1
  # => ['Daniel', 'i@heart.rails']

(I was unreasonably nervous to leave this comment. I actually wrote it, then deleted it, then wrote it again.)

@@ -793,6 +793,11 @@ def test_pluck_loaded_relation_sql_fragment
end
end

def test_pick
assert_equal "The First Topic", Topic.order(:id).pick(:heading)
assert_nil Topic.where(id: 9999999999999999999).pick(:heading)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failing the assert will be fixed by #30000.

@matthewd
Copy link
Member

matthewd commented Feb 9, 2018

is primarily useful when you have a relation that's already narrowed down to a single row

This reminds me of some of the philosophy behind #26206. Might it be worth codifying that rule?

def pick(*column_names)
  rows = limit(2).pluck(*column_names)
  raise "Too many rows" if rows.size > 1
  rows.first
end

@dhh
Copy link
Member Author

dhh commented Feb 9, 2018

The specific relation that I extracted #pick from actually does comprise multiple rows, but I know that I just want to deal with the first one (because I ordered the relation in such a way. Here's the actual source:

module Person::Creator
  extend ActiveSupport::Concern

  def last_created_at_from(recordings)
    recordings.where(creator: self).reverse_chronologically.pick(:created_at)
  end
end

So a guard clause against multiple rows wouldn't really work for this. I don't think it's necessary in any case.

@dhh
Copy link
Member Author

dhh commented Feb 9, 2018

@composerinteralia Totally. Just added that 👌

@kamipo I just changed the test for now 🙏

@dhh dhh merged commit 80cc0d3 into master Feb 9, 2018
@bogdan
Copy link
Contributor

bogdan commented Feb 10, 2018

Will it raise NoMethodError for NullRelation?

@rafaelfranca
Copy link
Member

@bogdan no. Is it raising? I just added 4c615a5 and that test is passing. As it is the only way to generate NullRelation I can't see how it would be raising.

@bogdan
Copy link
Contributor

bogdan commented Feb 13, 2018

@rafaelfranca It was only my theoretical guess. I think the test is good idea anyway. Thanks!

kamipo added a commit that referenced this pull request Feb 18, 2018
@ohaddahan
Copy link

@dhh

The value is, again like with pluck, typecast by the column type.

pg gem (and mysql2) support C typecasting , we did a proof of concept at https://gitlab.com/nativepluck/nativepluck

(local tests on mysql2 gem showed performance improvements too)

Will be happy to pitch in and get this into Rails if this seems worth a while.
(I believe the benchmarks show it is)

@rafaelfranca
Copy link
Member

While the database clients supports C typecasting plunk and pick use the types that sometimes are defined by the user using the attributes API. We can of course use C typecasting for the native types, but even those behave differently from the types defined in the database clients so we can't just start to use them.

@ohaddahan
Copy link

@rafaelfranca I assume you refer mostly to the more "exotic" types, right?

Given the significant performance boost, I personally think it's good to have, or at least have as an option. I believe most people won't see any difference switching to native typecasting.

But that's for Rails core team to decide, not me.

@henrik
Copy link
Contributor

henrik commented Feb 22, 2019

Could a name like pluck_first make sense? I know it's longer, but it would make the difference between pluck and this method clearer. I blogged about this thinking here: https://thepugautomatic.com/2017/01/the-pairing-test/

(I also considered pluck_one, but that'd be confusing with something like users.in_order.pluck_one(:name, :email) – is it just one out of name and email? pluck_first does not have that problem.)

Rails currently has a few bits of naming that can be confusing not only to new devs but also experienced ones – I've worked in Rails for quite a few years now and I still get unsure about validate vs validates sometimes :) I'm seeing a similar (but lesser) risk here, especially for devs new to the framework.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants