1

I'm coming back to Ruby after a long time away.

I've written the following code which is functional:

def generate_address_record(data)
 address = FM[:fap_address].build do |a|
        if data['Line 1'].blank?
          a.unstructured_address.line1 = nil
        else
          a.unstructured_address.line1 = data['Line 1']
        end

        if data['Line 2'].blank?
          a.unstructured_address.line2 = nil
        else
          a.unstructured_address.line2 = data['Line 2']
        end

        if data['Line 3'].blank?
          a.unstructured_address.line3 = nil
        else
          a.unstructured_address.line3 = data['Line 3']
        end

        if data['Line 4'].blank?
          a.unstructured_address.line4 = nil
        else
          a.unstructured_address.line4 = data['Line 4']
        end

        if data['Line 5'].blank?
          a.unstructured_address.line5 = nil
        else
          a.unstructured_address.line5 = data['Line 5']
        end

        if data['Postcode'].blank?
          a.unstructured_address.postcode = nil
        else
          a.unstructured_address.postcode = data['Postcode']
        end
  end
end

Is there a way this can be re-written in a 'nicer' way in one loop so that I don't need all of these individual if statements.

Any advice would be greatly appreciated.

1
  • 2
    There is no such method as blank? in Ruby. Commented Aug 23, 2019 at 8:12

4 Answers 4

2

If data contains only expected values, you can convert key to the method name.

def generate_address_record(data)
  address = FM[:fap_address].build do |a|
      data.each do |key, value|
          name = key.gsub(/[[:space:]]/, '').downcase
          a.unstructured_address.public_send("#{name}=", value.presence)
      end
  end
end

Be careful(or don't use this approach) when hash keys are coming from outside of the application or other uncontrolled environment.

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

4 Comments

I generally prefer public_send over send. send does two things: send a message with a dynamic name and ignore access restrictions. In this case, you only need the former, so you can use public_send, and future readers of your code don't have to wonder why you are circumventing access restrictions.
don't use send, use public_send as Jörg already mentioned. Also I would not recommend this approach, just go with what spickerman recommended. It is much easier to understand and extend. There will be no security problems (what if a field is called destroy) either.
@Pascal, I think quite unlikely the setter destroy= will destroy the object. On other hand as I mentioned in the answer, use it if you are sure that hash keys are always same as expected class attributes. Of course you shouldn't use this where hash keys are passed from the uncontrolled environment. But that exactly what Rails do with request parameters when passing it straight to the model builder "yak" ;)
ah true, the =, missed. You have to permit params in Rails when passing them into a model.
2

Yup, you can use #presence (I'm assuming you're using Rails):

a.unstructured_address.line1 = data['Line 1'].presence

#presence behaviour:

''.presence
# => nil
nil.presence
# => nil
'a'.presence
# => "a"
false.presence
# => nil

4 Comments

You don't need || nil, data['Line 2'].presence is enough.
.presence will assign to nil if blank? Excellent, thank you
@Tom yes, look at its source: apidock.com/rails/Object/presence
This is the nicest and most easily read solution in my opinion
2

I propose this combination of the various already posted solutions because I think it has a good balance between shortness and readability:

def generate_address_record(data)
  address = FM[:fap_address].build do |a|
    a.unstructured_address.line1    = data['Line 1'].presence
    a.unstructured_address.line2    = data['Line 2'].presence
    a.unstructured_address.line3    = data['Line 3'].presence
    a.unstructured_address.line4    = data['Line 4'].presence
    a.unstructured_address.line5    = data['Line 5'].presence
    a.unstructured_address.postcode = data['Postcode'].presence
  end
end

2 Comments

This is much better than using send (or better public_send) just to save some lines of code.
you could remove the assignment address = . And pass the unstructured_address in the block directly, so the a. can be removed.
0

One pattern I find usefull is to make a hash and then iterate over it:

def generate_address_record(data)
   address = FM[:fap_address].build do |a|
     {
       "Line 1" =>  :line1, 
       "Line 2" =>  :line2,
       "Line 3" =>  :line3,
       "Line 4" =>  :line4,
       "Line 5" =>  :line5,
       "Postcode" => :postcode
     }.each do |key, accessor|
       if data[key].blank?
         a.unstructured_address.send(accessor) = nil
       else
         a.unstructured_address.send(:"#{accessor}=") = data[key]
       end
     end
  end
end

You can also use this with presence as mrzasa shared

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.