0

How could I dynamic regex parsing with method

Current method

@product.price = line.gsub(/,/, '').scan(/\d{3,9}/).first
@product.model = line.rm_dirty.split(":").last.strip
@product.location = line.split(":").last.strip

Expected method # pass the string operations to another function

However, I alway got NoMethodError: undefined method `gsub(/,/, '')' for

def fetch_by_keyword_and_regex(regex_str)
  @line.send(regex_str)
end

fetch_by_keyword_and_regex("gsub(/,/, '').scan(/\d{3,9}/).first")
2
  • What is regex_str normally like? Commented May 2, 2016 at 7:27
  • Your second code block only contains the @product.price part, what about model and location? Commented May 2, 2016 at 7:44

3 Answers 3

1

If you want to make this more dynamic you can send in a block:

line_sub = lambda { |line|
  line.gsub(/,/, '').scan(/\d{3,9}/).first
}

Then you can use this on anything:

line_sub.call(@line)

This is a lot like JavaScript declaring var line_sub = function(line) { ... }.

The send method only allows making a particular method call by name:

@line.send(:sub, /,/, '').send(:scan, /\d{3,9}/).send(:first)

That's something you could express in an array, like:

line_process = [
  [ :sub, /,/, '' ],
  [ :scan, /\d{3,9}/ ],
  [ :first ]
]

Then use that like:

line_process.inject(@line) do |obj, args|
  obj.send(*args)
end

This is quite a mess, so I'd avoid it whenever possible. Ruby's block-passing semantics are a lot cleaner for metaprogramming.

Sign up to request clarification or add additional context in comments.

Comments

0

You would have to use one of Ruby's evaluation functions, i.e. eval, instance_eval or instance_exec, but that's not recommended.

I would yield the object instead: (assuming that your actual implementation / use case is more complex)

def fetch(regex_str)
  yield @line
end

fetch { |l| l.gsub(/,/, '').scan(/\d{3,9}/).first }

2 Comments

@tadman just like the OP's fetch_by_keyword_and_regex. I suppose the actual implementation is more complex.
I see what you're doing now, but an attr_accessor might be more convenient in practice.
0

If I understand it correctly, I question the approach altogether. Reasons:

1) metaprogramming should be done as a last resort, when less obtuse approaches fail to satisfy the need,

2) the variable stuff in question is code/functionality, and should be expressed as such -- expressing it as a string and then eval'ing it, etc., is redundant and overly complex, and

3) this approach will, to some extent, conceal to the human reader, and definitely to the code editor, analysis tool, etc., what is really going on.

One alternative approach would be a hash whose keys are the attribute names, and values are the code to apply to the values, essentially a lookup table for attribute processing functionality, e.g.

ATTRIBUTE_PROCESSORS = {
  price:    ->(line) { line.gsub(/,/, '').scan(/\d{3,9}/).first },
  model:    ->(line) { line.rm_dirty.split(":").last.strip },
  location: ->(line) { line.split(":").last.strip }
}

...and call it like this:

ATTRIBUTE_PROCESSORS[:price].(line)

A side benefit of this is that all your attribute processing is colocated, so you can easily spot duplication, and refactor as appropriate.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.