# CreativeRailsUtilities

Defines extensions, useful and convenient methods on Ruby and Rails base classes.  
Requires `Ruby >=2.1` for keyword arguments.  
Intended for `Rails >= 4.0.0`, but will probably work for older projects with robust Activesupport inclusion.

## Installation

`gem 'creative_rails_utilities', "~> 0.4.0"` and `bundle`

## Usage

Feel free to read the source under `/lib/creative_rails_utilities/` and peruse the method "it" lines under `/spec`

##### Date

```ruby
# get an array of date objects
Date.build_date_array("2015-10-11", "2015-10-13") #=> ["2015-10-11".to_date, "2015-10-12".to_date "2015-10-13".to_date]
```

```ruby
# get an array of dates between one date object and another
"2015-10-11".to_date.build_date_array("2015-10-09") #=> ["2015-10-09".to_date, "2015-10-10".to_date, "2015-10-11".to_date]
```

```ruby
# build a range of dates that will never go beyond a limit and/or yesterday
# used for accessing pre-churned data for strictly past days

# Called on "2015-10-13"
"2015-10-11".to_date.array_with_pre_churn_limit(30) #=> ["2015-10-11".to_date, "2015-10-12".to_date]

# Called on "2015-12-25"
"2015-10-11".to_date.array_with_pre_churn_limit(30) #=> ["2015-10-11".to_date, .. 28 .. , "2015-11-10".to_date ]
```

##### Hash

```ruby
# dig for deep nested values in a hash safely
a = {a: {b: {c: {d: 1}}}}
a.dig(:a, :b, :c, :d) #=> 1
a.dig(:a, :b, :c, :x) #=> nil
```

```ruby
# build a hash from variables and methods, more at https://github.com/Epigene/hashrush
@key1 = "a"
def key2; return "b"; end

Hash.rush(binding, :@key1, :key2)
#=> {key1: "a", key2: "b"}
```

```ruby
# fast sort by key
some_hash.fast_sort_keys #=> some_sorted_hash
```

```ruby
# ensure a hash has a range of integer keys (useful for graphs)
{}.populate_with_keys(min: 1, max: 3) #=> {1 => 0, 2 => 0, 3 => 0}
```

##### Numeric

```ruby
# integer into letters
1.to_s26 #=> "a"
28.to_s26 #=> "ab"
# NB, not to be confused with 28.to_s(26) which operates as base (n) interpretation and starts with numbers.
```

```ruby
# get a part even when deleting with zero
# whole.safe_part(part)
100.safe_part(50) #=> 0.5
1.safe_part(2) #=> 2
0.safe_part(0) #=> 0
```

```ruby
# get a percent even when deleting with zero
# whole.safe_percent(part)
100.safe_percent(50) #=> 50
1.safe_percent(2) #=> 200
3.0.safe_percent(1) #=> 33.333333333333336 # you should .round when calling this on floats
3.0.safe_percent(1, precision: 2) #=> 33.33 # As of 0.3.4 you can have precision
0.safe_percent(0) #=> 0
```

```ruby
# get a portion even when deleting with zero
# whole.safe_per(part)
100.safe_per(50) #=> 2.0
3.12.safe_per(2.6) #=> 1.2
1.safe_per(3) #=> 0.3333333333333333
0.safe_per(0) #=> 0
```

```ruby
# transform a Numeric that denotes seconds into a hash of how many :days, :hours, :minutes ad :seconds that is
61.5.to_time_hash #=> {days: 0, hours: 0, minutes: 1, seconds: 1.5}
2775721.to_time_hash #=> {days: 32, hours: 3, minutes: 2, seconds: 1}
(Time.zone.now.to_i - 3.minutes.ago.to_i).to_time_hash #=> {days: 0, hours: 0, minutes: 3, seconds: 0}
```

##### Object

__Object.chain_if(switch, operator, *args)__
__Object#chain_if(switch, operator, *args)__
```rb
# Allows smart conditional chaining of method calls or Procs
# Works on any type of object, class or instance

String.chain_if(true, :new, "abc") #=> "abc"
String.chain_if(false, :new, "abc") #=> String
String.chain_if(true, Proc.new{|a| a.new("abc") }) #=> "abc"
String.chain_if(false, Proc.new{|a| a.new("abc") }) #=> String

1.chain_if(false, Proc.new{raise}, 0).chain_if("yes", :*, 2).chain_if(!nil, Proc.new{|a| a.to_s}) #=> "2"
# Here `chain_if(false, Proc.new{raise}, 0)` would raise, but it is set to false, so it is skipped.
# `chain_if("yes", :*, 2)` multiplies by 2, and is ON (since "yes" is truthy)
# Finally, `chain_if(!nil, Proc.new{|a| a.to_s})` turns result to a string and also is ON.
```

##### String

```ruby
# letters into integer
"z".to_i26 #=> 26
"apple".to_i26 #=> 749325
```

```ruby
# removes leading, trailing and repeated middle whitespace
"   z   z   ".clean_whitespace #=> "z z"
```

```ruby
# Unindent here doc type strings to the indentation level of first line
string = <<-ENDBAR.unindent
  1
    2
      3
  1
ENDBAR

string #=> "1..." instead of "  1..."
```

```ruby
# Convert string to boolean.
"true".to_bool #=> true
"faLSE".to_bool #=> false # yup, case insensitive
"0".to_bool #=> false
"".to_bool #=> false
"y".to_bool #=> true
```

#### View Helpers
Rails-only (via railtie) view helpers have been added, they are automagically loaded and usable upon gem inclusion.

##### relative_time_parse(earlier_time, later_time=optional)
turns a timestamp into "time ago" format.  
Examples:

```ruby
# If used with only one argument, will default the second argument to Time.now
# at "2015-01-15 12:00".to_datetime

relative_time_parse("2015-01-15 12:00".to_datetime)
#=> {key: "now", count: nil}
relative_time_parse("2015-01-15 11:59:59".to_datetime)
#=> {key: "second", count: nil}
relative_time_parse("2015-01-15 11:59:58".to_datetime)
#=> {key: "seconds", count: "1"}
relative_time_parse("2015-01-15 11:59:00".to_datetime)
#=> {key: "minute", count: nil}
relative_time_parse("2015-01-15 11:58:00".to_datetime)
#=> {key: "minutes", count: "2"}
relative_time_parse("2015-01-15 11:00".to_datetime)
#=> {key: "hour", count: nil}
relative_time_parse("2015-01-15 09:00".to_datetime)
#=> {key: "hours", count: "3"}
relative_time_parse("2015-01-14 11:00".to_datetime)
#=> {key: "day", count: nil}
relative_time_parse("2015-01-10 09:00".to_datetime)
#=> {key: "days", count: 5}

# if used with both arguments, expects the second to be later than the first and will calculate relative time between them
relative_time_parse("2015-01-01 12:00".to_datetime, "2015-01-01 12:02".to_datetime)
#=> {key: "minutes", count: "2"}
```

The keys are intended to be used together with `I18n.t`  
Sample yaml for English

```yml
en:
  time:
    now: "just now"
    second: "a second ago"
    seconds: "%{count} seconds ago"
    minute: "a minute ago"
    minutes: "%{count} minutes ago"
    hour: "an hour ago"
    hours: "%{count} hours ago"
    day: "a day ago"
    days: "%{count} days ago"
```

Then you can write a simple helper that specifies localization key location and returns the correct value based on the hash returned by `relative_time_parse`
```ruby
def relative_short_time(t1, t2=nil)
  hash = relative_time_parse(t1, t2)
  count_hash = hash.count.present? ? hash.except(:key): {}
  return I18n.t("time.#{hash[:key]}", count_hash)
end
```

## Development
Use ruby >=2.1

1. Write specs
2. Make specs fail
3. Write feature
4. Make tests pass
5. Update README.md with examples
6. Update CHANGELOG.md with changes/fixes/additions.
7. Bump version and `rake release`

## Contributing

Bug reports and pull requests are welcome on [GitHub](https://github.com/CreativePublisher/rails_utilities)

## License

The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).