Reputation: 2900
I want to calculate how long the user has been a member of my app. I should display it as:
User member for 2y, 9m
To do so I've created method inside of User model
def member_for
#calculate number of months
month = (Time.current.year * 12 + Time.current.month) - (created_at.year * 12 + created_at.month)
#an array [years, months]
result = month.divmod(12)
if result[0].zero? && result[1].nonzero?
"User member for #{result[1]}m"
elsif result[1].zero? && result[0].nonzero?
"User member for #{result[0]}y"
elsif result[0].zero? && result[1].zero?
'User member for 1m'
else
"User member for #{result[0]}y, #{result[1]}m"
end
end
But honestly this code smells, isn't there some built-in method in Rails6 to do this better and make the code look a little cleaner?
Upvotes: 2
Views: 1389
Reputation: 71
Here are a few succint solutions:
# In a view helper
def member_for(user)
"User member for #{distance_of_time_in_words_to_now(user.created_at)}"
end
# In a controller
def member_for
"User member for #{helpers.distance_of_time_in_words_to_now(@user.created_at)}"
end
# In a model
def member_for
"User member for #{ApplicationController.helpers.distance_of_time_in_words_to_now(created_at)}"
end
You could also place this in the view and skip the method:
<%= "User member for #{distance_of_time_in_words_to_now(@user.created_at)}" %>
Upvotes: 0
Reputation: 101831
If you want to avoid the caching/timezone headaches involved with distance_of_time_in_words
* which will essentially invalidate your cached responses/fragments continously then just output a HTML5 time element and use a JS library like moment.js to calculate the elapsed time and display it to the user:
module TimeHelper
def time_tag(date_or_time, **options)
tag.time(datetime: date_or_time.iso8601, **options)
end
end
$ yarn install moment
// app/javascripts/packs/application.js
require('moment');
<% @users.each do |user| %>
<p><%= time_tag(user.created_at, class: 'member-since') %></p>
<% end %>
document.querySelectorAll('time.member-since').forEach((el) => {
const m = moment(el.dateTime);
const str = `User has been a member for ${m.toNow(true)}`;
const textnode = document.createTextNode(str);
el.appendChild(textnode);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" integrity="sha512-qTXRIMyZIFb8iQcfjXWCO8+M5Tbc38Qi5WzdPOYZHIlZpzBHG3L3by84BBBOiRGiEb7KKtAOAs5qYdUiZiQNNQ==" crossorigin="anonymous"></script>
<p><time datetime="2001-05-15T19:00" class="member-since" /></p>
<p><time datetime="2005-05-15T09:00" class="member-since" /></p>
<p><time datetime="2021-04-15T09:00" class="member-since" /></p>
See Difference between two dates in years, months, days in JavaScript for alternatives to moment.js.
Upvotes: 0
Reputation: 1098
You can use ActiveSupport::Duration for this. All you need to do is pass the time difference to the ActiveSupport::Duration.build
method.
For example:
time_diff = Time.now - 1000.days.ago
ActiveSupport::Duration.build(time_diff.to_i) # 2 years, 8 months, 3 weeks, 5 days, 28 minutes, and 47 seconds
ActiveSupport::Duration.build(time_diff.to_i).parts # 2 years, 8 months, 3 weeks, 5 days, 28 minutes, and 47 seconds
Upvotes: 5