Providing defaults if params aren't present - ruby-on-rails

I'm trying to create a function that will return defaults if date parameters are not set.
If params[:start_time] not present? return DateTime.now and if params[:end_time] not present? return 1.week.from_now
I'd like to keep these two checks in one function but I can't get it working. If there a better way?
# Main search function
def self.search params, location
self
.join.not_booked
.close_to(Venue.close_to(location))
.activity(Activity.get_ids params[:activity])
.start_date(valid_date params[:start_time])
.endg_date(valid_date params[:end_time])
.ordered
end
# Check if date is nil
def self.valid_date date
if date
date.to_datetime
elsif date == params[:start_time]
DateTime.now
elsif date == params[:end_time]
1.week.from_now
end
end
Asked another way:
What's the best way to combine these two functions?
# Check if date is nil
def self.check_start date
date.present? ? date.to_datetime : DateTime.now
end
def self.check_end date
date.present? ? date.to_datetime : 1.week.from_now
end

If it's not a hard requirement to combine those two methods, you can simply and easily have these two different methods for checking the validity of start_time and end_time:
def self.validate_start_date start_date
start_date.present? ? start_date.to_datetime : DateTime.now
end
def self.validate_end_date end_date
end_date.present? ? end_date.to_datetime : 1.week.from_now
end
Then, in your main search function use them accordingly (start_date(validate_start_date params[:start_time]) and end_date(validate_end_date params[:end_time])):
# Main search function
def self.search params, location
self
.join.not_booked
.close_to(Venue.close_to(location))
.activity(Activity.get_ids params[:activity])
.start_date(validate_start_date params[:start_time])
.end_date(validate_end_date params[:end_time])
.ordered
end

Perhaps I'm misunderstanding but why not:
def self.check param
result = 1.week.from_now
if param[:end_time].present?
result = param[:end_time].to_datetime
end
return result
end
Your second "end_time" check will always overwrite any possible result from your "start_time" if we put it into one function.

Related

How do I find and replace 'nil' values of Ruby hash with "None" or 0?

I'm trying to drill down to each value in an iteration of an array nested hash and replace all nil values with something like 'None' or 0. Please see my code that is clearly not working. I need to fix this before I pass it to my Views in Rails for iteration and rendering:
My controller:
def show
results = Record.get_record(params[:trans_uuid])
if !results.empty?
record = results.map { |res| res.attributes.symbolize_keys }
#record = Record.replace_nil(record) # this calls method in Model
else
flash[:error] = 'No record found'
end
end
My model:
def self.replace_nil(record)
record.each do |r|
r.values == nil ? "None" : r.values
end
end
record looks like this when passed to Model method self.replace_nil(record:
[{:id=>1, :time_inserted=>Wed, 03 Apr 2019 15:41:06 UTC +00:00, :time_modified=>nil, :request_state=>"NY", :trans_uuid=>"fe27813c-561c-11e9-9284-0282b642e944", :sent_to_state=>-1, :completed=>-1, :record_found=>-1, :retry_flag=>-1, :chargeable=>-1, :note=>"", :bridge_resultcode=>"xxxx", :bridge_charges=>-1}]
each won't "persist" the value you're yielding within the block. Try map instead.
def self.replace_nil(record)
record.map do |r|
r.values.nil? ? "None" : r.values
end
end
In fact, there's a method for that; transform_values:
record.transform_values do |value|
value.nil? ? 'None' : value
end
I realized that using Rails you can use just presence and the or operator:
record.transform_values do |value|
value.presence || 'None'
end

Passing from a Controller to a Model

Pretty new to RoR. Wonder if anyone can help me with this issue.
I got a gem called "business_time" which calculates the business days between two dates. I have set up a method in the model which does all the calculations.
I have a field called "credit" which should hold the number of business days. Here's what I have:
MODEL
def self.calculate(from_date,to_date)
days = 0
date_1 = Date.parse(from_date)
date 2 = Date.parse(to_date)
days = date_1.business_days_until(date2)
days
end
CONTROLLER
def new
#vacation = current_user.vacations.build
#vacations = Vacation.calculate(:from_date, :to_date)
end
I got an error referencing something about a string.
Furthermore, how do I go about storing the data from the method into the field called "credit"?
Thanks guys.
I think there is no need for an extra method, since all attributes (from_date, end_date and credit) are stored in the same model.
I would just set from_date and end_date in the initializer and calculate credit with a callback before validation:
# in the model
before_validation :calculate_credit
private
def calculate_credit
if from_date && to_date
# `+ 1` because the user takes off both days (`from_date` and `to_date`),
# but `business_days_until` doesn't count the `from_day`.
self.credit = from_date.business_days_until(to_date) + 1
end
end
# in the controller
def new
#vacation = current_user.vacations.build
end
def create
#vacation = current_user.vacations.build(vacation_params)
if #vacation.save
# #vacation.credit would return the calculated credit at this point
else
# ...
end
end
private
def vacation_params
params.require(:vacation).permit(:from_date, :to_date)
end
What you need here is pass String objects instead of Symbol objects.
So instead of #vacations = Vacation.calculate(:from_date, :to_date), you probably need to pass params[:from_date] and params[:to_date] which should be strings like 20/01/2016, etc...
Your code should be
#vacations = Vacation.calculate(params[:from_date], params[:to_date])

Parsing daterange param as two attributes - Rails 4, Datetimepicker GEM

I have a model Foo with two datetime attributes called start_time and end_time. Foo has a validation logic for each attribute:
Model:
...
validate :dates_logic_validation, on: :save
...
def dates_logic_validation
if start_time < Time.now
errors.add(:start_time, 'Start time must be greater or equal to today\'\s date')
return false
elsif start_time > end_time
errors.add(:end_time, 'End time must be greater than start time')
return false
else
true
end
end
The validation error handling works fine when I try to create / update Foo via the console. Using datetimepicker gem, I am getting both dates in params hash. In my foos#create, I want to be able to parse this daterange param and assign it foo.start_time and foo.end_time. To do so, I added an instance method:
def parse_date_range(daterange) #<= param is passed as "12/02/2015 - 01/14/2016"
start_time = daterange.split("-")[0].strip!
end_time = daterange.split("-")[1].strip!
update_attributes(start_time: start_time, end_time: end_time)
end
then calling it like so:
Controller (#create):
def create
#foo= Foo.new(foo_params)
#foo.parse_date_range(params[:daterange])
if #foo.valid?
#foo.save
redirect_to foos_path(#foo)
else
render :new
end
end
and here is where the music stops. If I log #{start_time} and #{end_time} after I call parse_data_range on #foo, both will be empty. Which suggests that I probably can't call update_attributes on a non-existing object. If that's the case, what would be the best solution?
I think it's a mistake to do a second database query (update_attributes) when instead you can extract the dates from params in the controller like this:
class FoosController < ApplicationController
before_action :extract_start_end_times, only: :create
def create
#foo = Foo.new(foo_params)
if #foo.valid?
#foo.save
redirect_to foos_path(#foo)
else
render :new
end
end
# ...
private
def extract_start_end_times
return unless params[:daterange].present?
daterange = params.delete(:daterange)
start_time, end_time = daterange.split(/\s*-\s*/)
.map {|date| Date.strptime(date, '%m/%d/%Y') }
params.merge!(start_time: start_time, end_time: end_time)
end
end
Note the use of Date.strptime(date, '%m/%d/%Y'). This is necessary because your dates are in an ambiguous format (Ruby has no way to know if "12/02/2015" is Dec. 2 or Feb. 12).
If you're using strong parameters you may have to modify your foo_params method to accommodate the start_time and end_time params.
This should get to work.
update_attributes(start_time: Date.parse(start_time), end_time: Date.parse(end_time))
You can also use
DateTime.parse("12/02/2015")
=> Thu, 12 Feb 2015 00:00:00 +0000

What is the best way to handle date in a Rails Form Objects

I'm using Form Object as described in 7 Patterns to Refactor Fat ActiveRecord Models #3 and currently I have an issue with storing date.
Here's what I've done:
class MyModelForm
# ...
def initialize(my_model: MyModel.new, params: {})
#my_model, #params = my_model, params
#my_model.date_field_on = Date.from_params #params, "date_field_on" if #params.present?
end
end
Where Date.from_params is implemented like:
class Date
def self.from_params(params, field_name)
begin
year = params["#{ field_name }(1i)"]
month = params["#{ field_name }(2i)"]
day = params["#{ field_name }(3i)"]
Date.civil year.to_i, month.to_i, day.to_i if year && month && day
rescue ArgumentError => e
# catch that because I don't want getting error when date cannot be parsed (invalid)
end
end
end
I cannot just use #my_model.assign_attributes #params.slice(*ACCEPTED_ATTRIBUTES) because my params["date_field_on(<n>i)"] will be skipped and date will not be stored.
Is there a better approach to handle date fields using Form Objects?
As #davidfurber mentioned in comments it works great with Virtus gem.
class MyModelForm
include Virtus
# ...
attribute :date_field_on, Date
def initialize(params: {})
#my_model.assign_attributes params
end
end

mongo_mapper custom data types for localization

i have created a LocalizedString custom data type for storing / displaying translations using mongo_mapper.
This works for one field but as soon as i introduce another field they get written over each and display only one value for both fields. The to_mongo and from_mongo seem to be not workings properly. Please can any one help with this ? her is the code :
class LocalizedString
attr_accessor :translations
def self.from_mongo(value)
puts self.inspect
#translations ||= if value.is_a?(Hash)
value
elsif value.nil?
{}
else
{ I18n.locale.to_s => value }
end
#translations[I18n.locale.to_s]
end
def self.to_mongo(value)
puts self.inspect
if value.is_a?(Hash)
#translations = value
else
#translations[I18n.locale.to_s] = value
end
#translations
end
end
Thank alot
Rick
The problem is that from within your [to|from]_mongo methods, #translations refers to a class variable, not the instance variable you expect. So what's happening is that each time from_mongo is called, it overwrites the value.
A fixed version would be something like this:
class LocalizedString
attr_accessor :translations
def initialize( translations = {} )
#translations = translations
end
def self.from_mongo(value)
if value.is_a?(Hash)
LocalizedString.new(value)
elsif value.nil?
LocalizedString.new()
else
LocalizedString.new( { I18n.locale.to_s => value })
end
end
def self.to_mongo(value)
value.translations if value.present?
end
end
I found that jared's response didn't work for me -- I would get that translations was not found when using LocalizedString in an EmbeddedDocument.
I would get a similar problem on rick's solution where translations was nil when using embedded documents. To get a working solution, I took Rick's solution, changed the translation variable to be an instance variable so it wouldn't be overwritten for each new field that used LocalizedString, and then added a check to make sure translations wasn't nil (and create a new Hash if it was).
Of all the LocalizedString solutions floating around, this is the first time I've been able to get it working on EmbeddedDocuments and without the overwritting problem -- there still may be other issues! :)
class LocalizedString
attr_accessor :translations
def self.from_mongo(value)
puts self.inspect
translations ||= if value.is_a?(Hash)
value
elsif value.nil?
{}
else
{ I18n.locale.to_s => value }
end
translations[I18n.locale.to_s]
end
def self.to_mongo(value)
puts self.inspect
if value.is_a?(Hash)
translations = value
else
if translations.nil?
translations = Hash.new()
end
translations[I18n.locale.to_s] = value
end
translations
end
end
I found this post: which was very helpful. He extended HashWithIndifferentAccess to work as a LocalizedString. The only thing I didn't like about it was having to explicly specify the locale when setting it each time -- I wanted it to work more like a string. of course, you can't overload the = operator (at least I don't think you can) so I used <<, and added a to_s method that would output the string of the current locale....
class LocalizedString < HashWithIndifferentAccess
def self.from_mongo(value)
LocalizedString.new(value || {})
end
def available_locales
symbolize_keys.keys
end
def to_s
self[I18n.locale]
end
def in_current_locale=(value)
self[I18n.locale] = value
end
def << (value)
self[I18n.locale] = value
end
end
and then I have a class like:
class SimpleModel
include MongoMapper::Document
key :test, LocalizedString
end
and can do things like
I18n.locale = :en
a = SimpleModel.new
a.test << "English"
I18n.locale = :de
a.test << "German"
puts a.test # access the translation for the current locale
I18n.locale = :en
puts a.test # access the translation for the current locale
puts a.test[:de] # access a translation explicitly
puts a.test[:en]
puts a.test.inspect
and get
German
English
German
English
{"en"=>"English", "de"=>"German"}
so there we go -- this one actually seems to work for me. Comments welcome, and hope this helps someone!

Resources