1

I have a hash like this:

@password_constraints = {
  length: 'Must be at least 6 character long.',
  contain: [
      'one number',
      'one lowercase letter',
      'one uppercase letter.'
  ]
}

And I want to write a method that returns an human-readable string built from this hash like this one:

Must be at least 6 character long. Must contain: one number, one lowercase letter and an uppercase letter.

Currently, I have a very verbose method with an each that iterates over the @password_constraints[:contain] array and has several conditions to check if I have to put a ,, or an and or nothing.

But I want almost the same behavior as join(', ') but the last delimiter must be and.

I'm looking for some sort of solution like this one with the special join:

def password_constraints
  @password_constraints[:length] << ' Must contain.' << @password_constraints[:contain].join(', ') 
end

Is there a way to make this more compact or Ruby-like?

0

3 Answers 3

4

You can use the splat operator to split the array and just check the case if there's only one string:

def message(constraints)
  *others, second_last, last = constraints[:contain]
  second_last += " and #{last}" unless last.nil?
  "#{constraints[:length]} Must contain: #{(others << second_last).join(', ')}"
end

@password_constraints = {
  length: 'Must be at least 6 character long.',
  contain: [
      'one number',
      'one lowercase letter',
      'one uppercase letter.'
  ]
}

message(@password_constraints)
# => "Must be at least 6 character long. Must contain: one number, one lowercase letter and one uppercase letter." 

# if @password_constraints[:contain] = ['one lowercase letter', 'one uppercase letter.']
message(@password_constraints)
# => "Must be at least 6 character long. Must contain: one lowercase letter and one uppercase letter." 

# if @password_constraints[:contain] = ['one lowercase letter']
message(@password_constraints)
# => "Must be at least 6 character long. Must contain: one lowercase letter"
Sign up to request clarification or add additional context in comments.

7 Comments

This is definitely a more Ruby-like answer. Using the splat operator to pull the last element, and using string interpolation instead of concatenation is preferred for clarity. If you really wanted to get funky, you could change the second bit of interpolated code in the output string to be "#{(others << second_last) * ', '}", but that's a lot harder to read.
Such an elegant solution and very compact. It's definitely what I was looking for. Thank you so much!
I update the solution: the second line of the method should be removed and it should be at the end of the third one, in order to not modify the current hash. *others, second_last, last = constraints[:contain] "#{constraints[:length]} Must contain: #{(others << second_last).join(', ')}" << " and #{last}" unless last.nil?
I don't know...You problably should create another question on SO with this new problem. But before that, if you are using Rails, you want to check Array#to_sentence
Thank you for the help Doguita, I've already found the problem.The problem was that I was actually modifying the hash every time I called the function hahah. Greetings!
|
0

I would use a monkey patch here for succinctness (yeah my join_requirements method could probably look a little nicer).

class Array
  def join_requirements
    result = ""
    self.each do |requirement|
      if requirement == self.last
        result << "and " << requirement
      else
        result << requirement + ", "
      end
    end
    result
  end
end

Then in your code you just see this:

contain = ['one number','one lowercase letter','one uppercase letter.']
puts contain.join_requirements
# => one number, one lowercase letter, and one uppercase letter.

3 Comments

Thank you so much for this solution, I've never heard before about monkey patches. It works perfectly. The problem is that in this project I only need this behavior for one method. But in other projects where the 'and problem' is more frequent, it will be the best solution! Thanks again.
While this works, it isn't more compact, and is no more "Ruby like".
@theTinMan that's fair, I did mention in the answer it wasn't the optimal way to write it. I just wanted to offer up monkey patching as a solution.
-1

Here's a fun little shorthand trick in Ruby that you can do with arrays to cut out the need for a verbose join.

def password_constraints
  @password_constraints[:length] << ' Must contain.' << @password_constraints[:contain] * ", "
end

The Array class' * operator explicitly checks for a string second operand, then converts to calling join under the covers.

2 Comments

Thank you for the info, there's a lot of magic inside ruby! hahah
This raises an error and, even fixed, don't add the 'and' in the final

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.