Created
July 15, 2011 04:13
-
-
Save jeffreyiacono/1084048 to your computer and use it in GitHub Desktop.
UI T/F select which sets a objects timestamp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Given that I have a model Item that inherits from ActiveRecord::Base and it has an attribute #locked_at, | |
# I want to be able to select T/F from a select in UI to set #locked_at's timestamp | |
# === VIEW === | |
# app/views/items/edit.html.haml, using formtastic | |
# Attempt #1: | |
# I could set locked_at => Time.now by setting it in the select's collection, | |
# but revisiting this form will not show the properly selected option, unless you do :selected => Time.now, | |
# which seems kludgy. Worse, if unlocked (locked_at => nil), this select option will not be | |
# selected because :selected => nil means "don't select anything" | |
= form.inputs do | |
= form.input :locked_at, :as => :select, :label => 'Status', :collection => {'Locked' => Time.now, 'Unlocked' => nil}, :selected => (item.locked_at ? Time.now : nil) | |
= form.buttons do | |
= form.commit_button :button_html => {:disable_with => 'Processing ...'} | |
# Attempt #2: | |
# What I really want is some new method, #locked, to either be true or false | |
# I'll potentially have to do some custom logic with :selected to make the correct status be selected | |
# I can't do form.input :locked because Item has no locked method yet | |
# And I need to make a note that params[:item][:locked] will equal 'true' / 'false', | |
# not true / false (String vs True/FalseClass) | |
# Let's use this and see what we need to do with the controller / model to get this working | |
= form.inputs do | |
= form.input :locked, :as => :select, :label => 'Status', :collection => {'Locked' => true, 'Unlocked' => false} | |
= form.buttons do | |
= form.commit_button :button_html => {:disable_with => 'Processing ...'} | |
# === CONTROLLER === | |
# app/controllers/items_controller.rb | |
# Attempt #1: | |
# Have to handle the passed params to set the timestamp | |
# Problem: I want to avoid doing any custom logic in the controller, along the lines of: | |
def update | |
@item = Item.find(params[:id]) | |
# Want to avoid the following check and keep controllers lean | |
# This is already messed up because params[:item][:locked] = 'true', which is a String, not true:TrueClass | |
# We can't just do ... if params[:item][:locked] b/c on false it will be 'false', which is truthy | |
# This is ok, but we can do better! | |
params[:item].merge(:locked_at => Time.now) if params[:item][:locked] == 'true' | |
@item.update_attributes(params[:item]) | |
flash[:notice] = "Yay!" | |
redirect_to root_path | |
# ^ of course do error handling and appropriate redirecting, omitted for this example | |
end | |
# Attempt #2: | |
# Let's just write what we wish we had and handle everything in the model (fingers crossed) | |
# Pretty standard stuff follows | |
def update | |
@item = Item.find(params[:id]) | |
@item.update_attributes(params[:item]) | |
flash[:notice] = "Yay!" | |
redirect_to root_path | |
# ^ of course do error handling and appropriate redirecting, omitted for this example | |
end | |
# === MODEL === | |
# app/model/item.rb | |
# Attempt #1: | |
# My sample model, has a column / attribute of :locked_at => datetime | |
# We need to define #locked so we don't have to do custom logic in the view | |
# to get the right select option to display and we need to define #locked= which | |
# needs to handle the 'true' vs 'false' string issue and sets locked_at to now appropriately | |
# This is a crappy solution, because it only covers for 'true', what about true, or t, or 1? | |
class Item < ActiveRecord::Base | |
def locked | |
!!read_attribute(:locked_at) | |
end | |
def locked=(value) | |
value = value == 'true' | |
write_attribute(:locked_at, (value ? Time.now : nil)) | |
end | |
end | |
# Attempt #2: | |
# Let's do what Rails does with its abstract definition of a column in a table. | |
# That is what #locked is here, in theory it is just another column. | |
# We could add a locked => boolean column to the the items table, but it's overkill | |
# and exposes us to the risk that these two columns could get out of sync. | |
# So what about this ... | |
# Source: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/column.rb | |
# We need to cast the passed value to a boolean ... | |
# ActiveRecord::ConnectionAdapters::Column.value_to_boolean to the rescue ... yay! | |
class Item < ActiveRecord::Base | |
def locked | |
!!read_attribute(:locked_at) | |
end | |
def locked=(value) | |
write_attribute(:locked_at, (ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value) ? Time.now : nil)) | |
end | |
end | |
# And some unit tests ... | |
# using RSpec, factory_girl, and Timecop | |
require 'spec_helper' | |
describe Item do | |
describe "#locked" do | |
let(:item) { Factory.create(:item) } | |
it "returns true if locked_at is non-nil" do | |
item.locked_at = Time.now | |
item.locked.should == true | |
end | |
it "returns false is locked_at is nil" do | |
item.locked_at = nil | |
item.locked.should == false | |
end | |
end | |
describe "#locked=" do | |
let(:item) { Factory.create(:item) } | |
before { Timecop.freeze } | |
after { Timecop.return } | |
it "sets locked_at to now if passed a non-nil value" do | |
item.locked = true | |
item.locked_at.should == Time.now | |
end | |
it "sets locked_at to now if passed 'true'" do | |
item.locked = 'true' | |
item.locked_at.should == Time.now | |
end | |
it "sets locked_at to nil if pass an empty string" do | |
item.locked = '' | |
item.locked_at.should == nil | |
end | |
it "sets locked_at to nil if passed false" do | |
item.locked = false | |
item.locked_at.should == nil | |
end | |
it "sets locked_at to nil if passed 'false'" do | |
item.locked = 'false' | |
item.locked_at.should == nil | |
end | |
it "sets locked_at to nil if passed nil" do | |
item.locked = nil | |
item.locked_at.should == nil | |
end | |
end | |
end | |
# Cucumber tests cover the interaction to make sure all parts are working properly | |
# Not shown here. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment