Configure and retrieve virtual attributes in the Rails model

I am looking for a rails-y method to approach the following:

Two datetime attributes in an Event :

 start_at: datetime end_at: datetime 

I would like to use 3 fields to access them in the form:

 event_date start_time end_time 

The problem is how to store the actual and virtual attributes in "sync" so that the model can be updated through the form and / or directly through start_at and end_at .

 class Event < ActiveRecord::Base attr_accessible :end_at, :start_at, :start_time, :end_time, :event_date attr_accessor :start_time, :end_time, :event_date after_initialize :get_datetimes # convert db format into accessors before_validation :set_datetimes # convert accessors into db format def get_datetimes if start_at && end_at self.event_date ||= start_at.to_date.to_s(:db) # yyyy-mm-dd self.start_time ||= "#{'%02d' % start_at.hour}:#{'%02d' % start_at.min}" self.end_time ||= "#{'%02d' % end_at.hour}:#{'%02d' % end_at.min}" end end def set_datetimes self.start_at = "#{event_date} #{start_time}:00" self.end_at = "#{event_date} #{end_time}:00" end end 

What works:

 1.9.3p194 :004 > e = Event.create(event_date: "2012-08-29", start_time: "18:00", end_time: "21:00") => #<Event id: 3, start_at: "2012-08-30 01:00:00", end_at: "2012-08-30 04:00:00", created_at: "2012-08-22 19:51:53", updated_at: "2012-08-22 19:51:53"> 

Until you set the actual attributes directly ( end_at will return to end_time when checking):

 1.9.3p194 :006 > e.end_at = "2012-08-30 06:00:00 UTC +00:00" => "2012-08-30 06:00:00 UTC +00:00" 1.9.3p194 :007 > e => #<Event id: 3, start_at: "2012-08-30 01:00:00", end_at: "2012-08-30 06:00:00", created_at: "2012-08-22 19:51:53", updated_at: "2012-08-22 19:51:53"> 1.9.3p194 :008 > e.save (0.1ms) BEGIN (0.4ms) UPDATE "events" SET "end_at" = '2012-08-30 04:00:00.000000', "start_at" = '2012-08-30 01:00:00.000000', "updated_at" = '2012-08-22 20:02:15.554913' WHERE "events"."id" = 3 (2.5ms) COMMIT => true 1.9.3p194 :009 > e => #<Event id: 3, start_at: "2012-08-30 01:00:00", end_at: "2012-08-30 04:00:00", created_at: "2012-08-22 19:51:53", updated_at: "2012-08-22 20:02:15"> 1.9.3p194 :010 > 

My assumption is that I also need to configure the “actual” attribute settings , but I'm not sure how to do this so as not to damage the default behavior. Thoughts? Perhaps there is a more "Rails-y" "callback-y" way to handle this?

+6
source share
2 answers

Here is my welcome. I did not test it with ActiveRecord, but I left comments. Hope this helps.

 class Event < ActiveRecord::Base attr_accessible :end_at, :start_at, :start_time, :end_time, :event_date attr_accessor :start_time, :end_time, :event_date def start_time @start_time || time_attr_from_datetime(start_at) end def start_time=(start_time_value) @start_time = start_time_value set_start_at end def end_time @end_time || time_attr_from_datetime(end_at) end def end_time=(end_time_value) @end_time = @end_time_value set_end_at end def event_date @event_date || start_at.to_date.to_s(:db) end def event_date=(event_date_value) @event_date = event_date_value set_start_at set_end_at end def start_at=(start_at_value) write_attribute(:start_at, start_at_value) # Maybe you need to do write_attribute(:start_at, DateTime.parse(start_at_value)) here ??? @start_time = time_attr_from_datetime(start_at) end def end_at=(end_at_value) write_attribute(:end_at, end_at_value) # Maybe you need to do write_attribute(:end_at, DateTime.parse(end_at_value)) here ??? @end_time = time_attr_from_datetime(end_at) end private def set_start_at self.start_at = DateTime.parse("#{event_date} #{start_time}:00") end def set_end_at self.end_at = DateTime.parse("#{event_date} #{end_time}:00") end def time_attr_from_datetime(datetime) "#{'%02d' % datetime.hour}:#{'%02d' % datetime.min}" end end 

EDIT: There is a specific template for getting and setting start_time and end_time. This may be partially abstracted by meta-programming, but I thought that would make the example obscure.

+6
source

I wouldn’t just “cache” the “virtual” attributes in general, esp, if you do not need your “virtual”, “custom” attributes, only “gettable”, and this is your example.

  def event_date start_at.to_date.to_s(:db) # yyyy-mm-dd end def start_time "#{'%02d' % start_at.hour}:#{'%02d' % start_at.min}" end def end_time "#{'%02d' % end_at.hour}:#{'%02d' % end_at.min}" end 

Once you start caching, you have to worry about the invalidation of cached values ​​- you basically have a problem with invalid cached values. There are several ways to do your original design work, but I don’t think the calculation done there is expensive enough to justify the added complexity of memoizing / caching how you do it. Just provide em on demand and you don’t have to worry about invalidated cached values.

If you really want to do what you originally offer, this may get you started: Callback for modified ActiveRecord attributes? (not sure if Rails has changed since stackoverflow was written though)

0
source

Source: https://habr.com/ru/post/923515/


All Articles