Extracting ids out of arguments of different types I'm building a Ruby API client. My brief is to extract ids out of various inputs and fetch the relevant data. It's working just fine at the moment but I think it's a little clumsy.

My specs (which pass):

shared_examples 'it requests ids' do |resource|
  it 'sends the ids to Foo::Client' do
    Foo.stub(:parse)
    Foo::Client.should_receive(resource).once.with(expected)
    Foo.send(resource, *args)
  end
end

describe Foo do
  describe 'resources have numeric ids only'
    [:accounts, :contacts].each do |resource|
      it_behaves_like 'it requests ids', resource do
        let(:args) { [1, 2] }
        let(:expected) { [1, 2] }
      end

      it_behaves_like 'it requests ids', resource do
        let(:args) { [3, 'jesse-pinkman', 5, {'links' =&gt; {resource.to_s =&gt; [98, 99]}}] }
        let(:expected) { [98, 99, 3, 5] }
      end
    end
  end

  describe 'resources have numeric and string ids'
    [:clients, :services].each do |resource|
      it_behaves_like 'it requests ids', resource do
        let(:args) { [2,'saul-goodman', 6] }
        let(:expected) { [2, 6, 'saul-goodman'] }
      end

      it_behaves_like 'it requests ids', resource do
        let(:args) { [3, 'saul-goodman', 5, {'links' =&gt; {resource.to_s =&gt; [202, 234]}}] }
        let(:expected) { [202, 234, 3, 5, 'saul-goodman'] }
      end
    end
  end
end


And my code:

module Foo
  class &lt;&lt; self
    [:accounts, :contacts, :clients, :services].each do |resource|
      define_method(resource) do |*args|
        ids = []
        unless args.empty?
          ids += hash_ids(args, resource)
          ids += numeric_ids(args)
          ids += text_ids(args) unless numeric_ids_only?(resource)
        end
        parse(client.send(resource, ids))
      end
    end

    private

    def hash_ids(args, resource)
      args.collect { |arg| arg['links'][resource.to_s] if arg.is_a? Hash }.compact!.flatten!.to_a
    end

    def numeric_ids(ids)
      ids.select { |id| !id.is_a?(Hash) &amp;&amp; id.to_i &gt; 0 }
    end

    def text_ids(ids)
      ids.select { |id| id.is_a? String }
    end

    def numeric_ids_only?(resource)
      [:accounts, :contacts].include?(resource)
    end

    def client
      Foo::Client
    end

    def parse(json)
      Foo::Adapter::JSON.parse(json)
    end
  end
end


My particular concern is the ugly concatenating of the ids array and passing all those args around. Any tips?
You should expect
Starting a couple of years back, rspec is moving to a new expectation syntax, you should consider porting to it:

shared_examples 'it requests ids' do |resource|
  it 'sends the ids to Foo::Client' do
    allow(Foo).to receive(:parse)

    expect(Foo::Client).to receive(resource).once.with(expected)

    Foo.send(resource, *args)
  end
end


Be more transparent
Your code hides pretty well the fact that clients and services is different from accounts and contacts - both in your code and in your tests.
It makes your code quite obfuscated (in the name of DRYness?) - you should make it clear that you have two different behaviors - and spell them out:

shared_examples 'resources have numeric ids only' do |resource|
  it_behaves_like 'it requests ids', resource do
    let(:args) { [1, 2] }
    let(:expected) { [1, 2] }
  end

  it_behaves_like 'it requests ids', resource do
    let(:args) { [3, 'jesse-pinkman', 5, {'links' =&gt; {resource.to_s =&gt; [98, 99]}}] }
    let(:expected) { [98, 99, 3, 5] }
  end
end

shared_examples 'resources have numeric and string ids' do |resource|
  it_behaves_like 'it requests ids', resource do
    let(:args) { [2,'saul-goodman', 6] }
    let(:expected) { [2, 6, 'saul-goodman'] }
  end

  it_behaves_like 'it requests ids', resource do
    let(:args) { [3, 'saul-goodman', 5, {'links' =&gt; {resource.to_s =&gt; [202, 234]}}] }
    let(:expected) { [202, 234, 3, 5, 'saul-goodman'] }
  end
end

it_behaves_like 'resources have numeric ids only', :accounts
it_behaves_like 'resources have numeric ids only', :contacts
it_behaves_like 'resources have numeric and string ids', :clients
it_behaves_like 'resources have numeric and string ids', :services


And in your code, I would rather use simple method delegation than meta-coding:

class &lt;&lt; self
  def accounts(*args)
    parse(client.accounts(hash_ids(args, :accounts) + numeric_ids(args))
  end

  def contacts(*args)
    parse(client.accounts(hash_ids(args, :contacts) + numeric_ids(args))
  end

  def clients(*args)
    parse(client.accounts(hash_ids(args, :clients) + numeric_ids(args) + text_ids(args))
  end

  def services(*args)
    parse(client.accounts(hash_ids(args, :services) + numeric_ids(args) + text_ids(args))
  end
end


It might be less DRY, but it is a lot clearer what your class actually does.