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

Refactor and clean up number helpers #10996

Merged
merged 1 commit into from Dec 3, 2013
Merged

Refactor and clean up number helpers #10996

merged 1 commit into from Dec 3, 2013

Conversation

mdespuits
Copy link

Reading through the source of the number helpers is a nightmare. There is very little that can be understood about the code aside from the documentation for each. This is intended to pull some of the hidden secrets of these helpers into their own classes.

  • Refactored all helpers to delegate to a NumberConverter object.
  • Extracted as much hidden knowledge as possible.
  • Eliminated need to hide private module methods
  • Eliminated as much shared/hidden duplication in the objects.

I don't think I am done as I still do not fully grasp a lot of the mathematical decisions being made, so help with naming would be much appreciated.

@egilburg
Copy link
Contributor

It's hard to read classes intermingled with methods that construct them, especially in a file 800 lines long. I'd create a number_converters directory and move all the converters in there (each in their own file), autoloaded by the number helper. It would also help with refactoring something common in several classes - such as trying to extract some new common behaviour into the parent class - because it's easier to flip between several open similarly-structured files and look in the same position in each, than it is to scroll up and down trying to hunt every subclass definition.

Only other concern I have is potential performance in case someone is using number helper massively - what is overhead of building all these objects on each call? Perhaps the invoking methods should cache each instance of a class (of course that would mean that passing the arguments would have to move from initializer to the convert method)

@mdespuits
Copy link
Author

I will work on moving these into their own directory and files. I like that approach much better as well.

@mdespuits
Copy link
Author

Yes, I admit that the performance may lag a bit. My goal wasn't performance in this round of refactoring. It was to clarify what each helper was doing under the hood rather than relying on documentation alone for understanding what the helper was doing.

I do not have benchmarks on this, but I'm guessing that using instantiated objects rather than functional class methods would be a performance hit if using these a lot. This can easily change by altering the classes to use class methods and remove instantiation.

@rafaelfranca
Copy link
Member

👍 I'm not sure if performance matters here. I'll review this better later but this is very good.

@mattdbridges great job ❤️

@ghost ghost assigned rafaelfranca Jun 18, 2013
-1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }

STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
autoload :NumberToRoundedConverter, "active_support/number_helper/number_to_rounded"
Copy link
Contributor

Choose a reason for hiding this comment

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

autoloads are lazy (as opposed to requires) and can be put anywhere in the class. Conventionally, similar to module includes, they are put at the very top for easier dependency tracking by maintainers. (in this case, above or right below extend self)

Also, if you add the line extend ActiveSupport::Autoload to the class, you can drop repeating the relative path on each autoloaded constant, as long as the file name exactly matches the class name (e.g. will involve renaming number_to_delimited to number_to_delimited_converter etc, but that's something you should try and do anyways, again for consistency for maintainers.

Taking it even one step further in class structure purism would involve creating a Converters namespace/directory and referring to these classes such as Converters::NumberToDelimited etc. You may or may not think this is an overkill, but just an idea.

@egilburg
Copy link
Contributor

Thanks for this PR, btw!

I also think Rails maintainers prefer pull requests squash their commits into one, especially if they all deal with the same area.


def initialize(number, opts = {})
@number = number
@opts = (opts || {}).symbolize_keys
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is || {} needed here? opts already default to {} in the method definition. It only matters if someone passes explicit nil or false as an the options argument, which I don't think makes sense.

Copy link
Author

Choose a reason for hiding this comment

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

I was a bit paranoid, I suppose. I suppose just allowing the error would make more sense than suppressing it in this context.

@mdespuits
Copy link
Author

Yes, I know they prefer squashed commits, but as I was unsure of how many more changes would be suggested, I figured it would be easier for me to continue to commit one at a time and then squash them once the Rails core team is ready.

@mdespuits
Copy link
Author

@rafaelfranca Would you want me to squash these commits now to make it easier for you to review?

@melekes
Copy link
Contributor

melekes commented Jun 20, 2013

Love this ❤️

@steveklabnik
Copy link
Member

@mattdbridges it'll need to be squashed before a merge, at least.

@mdespuits
Copy link
Author

bump

Due to the overall complexity of each method individually as well as the
global shared private module methods, this pulls each helper into it's
own converter class inheriting from a generic `NumberBuilder` class.

* The `NumberBuilder` class contains the private methods needed for each helper
method an eliminates the need for special definition of specialized private
module methods.
* The `ActiveSupport::NumberHelper::DEFAULTS` constant has been moved
into the `NumberBuilder` class because the `NumberBuilder` is the only
class which needs access to it.
* For each of the builders, the `#convert` method is broken down to
smaller parts and extracted into private methods for clarity of purpose.
* Most of the mutation that once was necessary has now been eliminated.
* Several of the mathematical operations for percentage, delimited, and
rounded have been moved into private methods to ease readability and
clarity.
* Internationalization is still a bit crufty, and definitely could be
improved, but it is functional and a bit easier to follow.

The following helpers were extracted into their respective classes.

* `#number_to_percentage` -> `NumberToPercentageConverter`
* `#number_to_delimited` -> `NumberToDelimitedConverter`
* `#number_to_phone` -> `NumberToPhoneConverter`
* `#number_to_currency` -> `NumberToCurrencyConverter`
* `#number_to_rounded` -> `NumberToRoundedConverter`
* `#number_to_human_size` -> `NumberToHumanSizeConverter`
* `#number_to_human` -> `NumberToHumanConverter`
@mdespuits
Copy link
Author

Anything else you guys need from me? /cc @rafaelfranca @steveklabnik

@steveklabnik
Copy link
Member

Looks good, but I'd prefer @rafaelfranca to merge

@rafaelfranca
Copy link
Member

I need to check some eager load related things with @josevalim but the code is good.

@mdespuits
Copy link
Author

@rafaelfranca Awesome!

@mdespuits
Copy link
Author

Bump.

@mdespuits
Copy link
Author

@josevalim @rafaelfranca Any update on this? Would really love to see this merged soon.

rafaelfranca added a commit that referenced this pull request Dec 3, 2013
Refactor and clean up number helpers

Conflicts:
	activesupport/lib/active_support/number_helper.rb
@rafaelfranca rafaelfranca merged commit 2da9d67 into rails:master Dec 3, 2013
@mdespuits mdespuits deleted the number-helper-refactor branch December 3, 2013 04:01
@mdespuits
Copy link
Author

😍 Thanks @rafaelfranca!

@mjtko
Copy link
Contributor

mjtko commented May 12, 2014

This change broke number_to_delimited when operating on ActiveSupport::SafeBuffer as the gsub! call was changed from a back-reference style to a block-style using $1 which cannot be used with ActiveSupport::SafeBuffer (see #1555).

I'll open up an issue to track this.

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.

None yet

7 participants