How can you convert a mysql datetime field into two form fields (1) date only, (2) time only, and combine both fields back into datetime format on form submit?
This would allow the use of the following gems, but store the dates in a single datetime field:
gem 'bootstrap-datepicker-rails'
gem 'bootstrap-timepicker-rails'
Thanks in advance!
Found the solution with help from #Althaf
Added virtual attributes to model.rb
Used before_save callback to convert back to datetime.
before_save :convert_to_datetime
def sched_date_field
sched_date.strftime("%d/%m/%Y") if sched_date.present?
end
def sched_time_field
sched_time.strftime("%I:%M%p") if sched_time.present?
end
def sched_date_field=(date)
# Change back to datetime friendly format
#sched_date_field = Date.parse(date).strftime("%Y-%m-%d")
end
def sched_time_field=(time)
# Change back to datetime friendly format
#sched_time_field = Time.parse(time).strftime("%H:%M:%S")
end
def convert_to_datetime
self.sched_time = DateTime.parse("#{#sched_date_field} #{#sched_time_field}")
end
Using Rails 4, needed to add sched_date_field and sched_time_field to strong params in controller.rb
Here are the fields in _form.html.erb
<%= f.label :sched_date_field, "Scheduled Date" %>
<%= f.text_field :sched_date_field, :class => "datepicker" %>
<%= f.label :sched_time_field, "Scheduled Time" %>
<%= f.text_field :sched_time_field, :class => "timepicker" %>
You can use date_time_attribute gem:
class MyModel < ActiveRecord::Base
include DateTimeAttribute
date_time_attribute :scheduled_at
end
It will allow you to set schedule_at_date and scheduled_at_time separately. Once attributes are set, values will be combined into schedule_at.
You could use virtual attributes See this Railscast and if you have a pro subscription the revised one.
Basically in the view you would the following
<%= f.label :date_field %>
<%= f.text :date_field %>
<%= f.label :time_field %>
<%= f.text :time_field %>
Your database would still keep a field which I'll call full_date
Now in your model you would have to define the above 2 fields as follows.
def date_field # What this returns will be what is shown in the field
full_date.strftime("%m-%d'%y") if full_date.present?
end
def time_field
full_date.strftime("%I:%M%p") if full_date.present?
end
def time_field=(time)
full_date = DateTime.parse("#{date_field} #{time_field})
end
Since it looks like you are using Rails 4, you'll have to permit date_field and time_field in your strong parameters.
Alternatively, I set up a solution in the controller that does all the datetime conversions before the object gets created, because changing the data in the model impacted all my tests and validations. "Event" is the object I'm creating here with the datetime values being assigned to it.
#In the controller:
def convert_to_datetime_and_assign(event, params)
date_field = Date.parse(params[:date_field]).strftime("%Y-%m-%d")
start_time_field = Time.parse(params[:start_time_field]).strftime("%H:%M:%S")
end_time_field = Time.parse(params[:end_time_field]).strftime("%H:%M:%S")
event.start_time = DateTime.parse("#{date_field} #{start_time_field}")
event.end_time = DateTime.parse("#{date_field} #{end_time_field}")
event
rescue ArgumentError
event.errors.add(:start_time, :invalid, message: "Date or time was invalid")
event
end
in the create and update controller methods I called the method above:
#event = convert_to_datetime_and_assign(#event, event_params)
I added fields for date_field, start_time_field and end_time_field in my forms for creating/updating "events". And in the model I added an accessor to be able to access those values.
attr_accessor :date_field, :start_time_field, :end_time_field
Related
I am using simple_form_for
<%= simple_form_for( #form_object, url: wizard_path, :method => :put) do |f| %>
<%= f.input :website %>
<%= f.submit %>
</div>
<% end %>
However, I am also using a Form Object
class Base
include ActiveModel::Model
# Validations
# Delegations
# Initializer
end
My issue is that my inputs are not mapping to my database columns, so https://github.com/plataformatec/simple_form#available-input-types-and-defaults-for-each-column-type
None of these show up ,and can I create custom mappings.
How can I allow Simple_form to see my column types and work correctly?
If I check the class of my delegated fields, they seem to show as :string or :integer, etc.
simple_form uses 2 methods to determine the input type field mapping from a standard model (type_for_attribute and has_attribute?). Source
Since you are wrapping the model in another layer but still want the inference that simple_form provides you just need to delegate these calls to the original model via
class Wrapper
include ActiveModel::Model
attr_reader :model
delegate :type_for_attribute, :has_attribute?, to: :model
def initialize(model)
#model = model
end
end
However if you were not wrapping the model you would need to define these methods yourself such as (using the new rails 5.2 Attribute API)
class NonWrapper
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name, :string
def type_for_attribute(name)
self.class.attribute_types[name]
end
def has_attribute?(name)
attributes.key?(name.to_s)
end
end
Example
a = NonWrapper.new(name: 'engineersmnky')
a.has_attribute?(:name)
#=> true
a.type_for_attribute(:name)
#=> => #<ActiveModel::Type::Value:0x00007fffcdeda790 #precision=nil, #scale=nil, #limit=nil>
Note other additions may be required for a form object like this to work with simple_form. This answer simply explains how to handle the input mapping inference
I'm new to Rails and am fixing a Rails 2 site. I have a form that lets the user add information for the starting location (:start) EITHER with an input OR with a dropdown field. However, I have found that when I include both options, only the dropdown (which comes last) submits data, while the input is ignored. What's the right way to include both options?
MY VIEW
<% form_for #newsavedmap, :html=>{:id=>'createaMap'} do |f| %>
<%= f.error_messages %>
<p>Enter a street address, city, and state:
<%= f.text_field :start, {:id=>"startinput", :size=>50}%></p>
<p>Or, select a location from the list:
<%= f.select :start, options_for_select(#itinerary.locations), {:include_blank => true }, {:id=>"startdrop"} %>
<input type="submit" id="savethismap" value="Save Map">
<% end %>
One way to achieve this is by using virtual attributes. Since both fields map to same attribute, you are going to have to pick which one to use.
# app/models/newsavedmap.rb
class Newsavedmap < ActiveRecord::Base
...
attr_accessible :start_text, :start_select
...
def start_text=(value)
#start_text = value if value
prepare_start
end
def start_select=(value)
#start_select = value if value
prepare_start
end
# start_text will fall back to self.start if #start_text is not set
def start_text
#start_text || self.start
end
# start_select will fall back to self.start if #start_select is not set
def start_select
#start_select || self.start
end
private
def prepare_start
# Pick one of the following or use however you see fit.
self.start = start_text if start_text
self.start = start_select if start_select
end
end
Then your form needs to use the virtual attributes:
<%= f.text_field :start_text, {:id=>"startinput", :size=>50}%></p>
<p>Or, select a location from the list:
<%= f.select :start_select, options_for_select(#itinerary.locations), {:include_blank => true }, {:id=>"startdrop"} %>
Other options are:
Use text_field as the primary and update it's value with selected option if user selects an option.
Add a hidden field in your form and use JavaScript to update the hidden field's value when text_field text gets updated or select option changes
How to make two separated fields so can be handled to one datetime field in database?
I'm using jquery datetime picker that puts values like value in code example below.
Parameters includes only field with time but ignores field with date.
If I comment field with time then date is sent and saved but parameter looks like "2013-23-02" and it isn't forwarded like multi parameter.
#model
field :start_at, type: DateTime
#view
= f.text_field :start_at, :value => "2013-02-23"
= f.text_field :start_at, :value => "20:20:20"
you can use virtual attributes and callback (this is not tested but you should get the idea)
# model
attr_writer :start_at_time, :start_at_date
before_validation :build_start_at
def start_at_date
#start_at_date ||= start_at.to_date
end
def start_at_time
#start_at_time ||= start_at.strftime("%H:%M")
end
def build_start_at
self.start_at = Time.parse "#{start_at_date} #{start_at_time}"
end
# view
= f.text_field :start_at_date
= f.text_field :start_at_time
We're using the "serialize" feature of ActiveRecord in Rails like this:
class User < ActiveRecord::Base
serialize :favorite_colors, Array
....
end
So we can have
u = User.last
u.favorite_colors = [ 'blue', 'red', 'grey' ]
u.save!
So basically ActiveRecord is serializing the array above and stores it in one database field called favorite_colors.
My question is: How do you allow a user to enter his favorite colors in a form?
Do you use a series of textfields? And once they're entered, how do you show them in a form for him to edit?
This is a question related to Rails Form Helpers for serialized array attribute.
Thanks
If you want multi-select HTML field, try:
= form_for #user do |f|
= f.select :favorite_colors, %w[full colors list], {}, :multiple => true
If you're using simple_form gem, you can present the options as check boxes easily:
= simple_form_for #user do |f|
= f.input :favorite_colors, as: :check_boxes, collection: %w[full colors list]
I have solved this problem by 'flattening' the array in the view and
reconstituting the array in the controller.
Some changes are needed in the model too, see below.
class User < ActiveRecord::Base
serialize :favorite_colors, Array
def self.create_virtual_attributes (*args)
args.each do |method_name|
10.times do |key|
define_method "#{method_name}_#{key}" do
end
define_method "#{method_name}_#{key}=" do
end
end
end
end
create_virtual_attributes :favorite_colors
end
If you don't define methods like the above, Rails would complain about the form element's
names in the view, such as "favorite_colors_0" (see below).
In the view, I dynamically create 10 text fields, favorite_colors_0, favorite_colors_1, etc.
<% 10.times do |key| %>
<%= form.label :favorite_color %>
<%= form.text_field "favorite_colors_#{key}", :value => #user.favorite_colors[key] %>
<% end %>
In the controller, I have to merge the favorite_colors_* text fields into an array BEFORE calling
save or update_attributes:
unless params[:user].select{|k,v| k =~ /^favorite_colors_/}.empty?
params[:user][:favorite_colors] = params[:user].select{|k,v| k =~ /^favorite_colors_/}.values.reject{|v| v.empty?}
params[:user].reject! {|k,v| k=~ /^favorite_colors_/}
end
One thing I'm doing is to hard-code 10, which limits how many elements you can have in the favorite_colors array. In the form, it also outputs 10 text fields. We can change 10 to 100 easily. But we will still have a limit. Your suggestion on how to remove this limit is welcome.
Hope you find this post useful.
To allow access to AR attributes, you have to grant them like this:
class User < ActiveRecord::Base
serialize :favorite_colors, Array
attr_accessible :favorite_colors
....
end
In the app I am building I have a need to combine multiple text fields into a single database column.
For example my "Business" entry has a column "Discount"
The text field I want to read something like this:
<%= f.text_field :discount %> % Off <%= f.text_field :discount %>.
I want both of these to be entered into the database as a string: "10% Off Shoes" (or whatever).
Is there a way to do this in Rails 3?
Thanks!
**Edit!
I tried Pan Thomakos's solution (using virtual attributes) and now I am getting the following error:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.split
Extracted source (around line #3):
1:
2: <%= f.label :cost %><br />
3: <%= f.text_field :percentage %> % Off <%= f.text_field :product %>.
app/models/business.rb:11:in `percentage'
I'm not really sure how to handle this! Admittedly I am weak when it comes to working within the model, I probably would have handled this in the controller.
Thanks!
Yes, the best way to do it is to use virtual attributes. Each virtual attribute will keep track of the different parts of the discount and the discount will be the combined field. Here is how I would implement it:
class Business
attr_writer :percentage, :product
before_save :create_discount
def percentage
#percentage.nil? ? discount.to_s.split('% Off ').first : #percentage
end
def product
#product.nil? ? discount.to_s.split('% Off ').last : #product
end
protected
def create_discount
discount = "#{#percentage}% Off #{#product}" unless #product.nil? || #percentage.nil?
end
end
You can then modify your view to:
<%= f.text_field :percentage %> % Off <%= f.text_field :product %>.
Switch the logic around.
class Business
attr_writer :percentage, :product
before_save :create_discount
def percentage
#percentage.nil? ? #percentage : discount.to_s.split('% Off ').first
end
def product
#product.nil? ? #product : discount.to_s.split('% Off ').last
end
protected
def create_discount
discount = "#{#percentage}% Off #{#product}" unless #product.nil? || #percentage.nil?
end
end