Skip to content

Instantly share code, notes, and snippets.

@jeffreyiacono
Created July 15, 2011 04:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeffreyiacono/1084048 to your computer and use it in GitHub Desktop.
Save jeffreyiacono/1084048 to your computer and use it in GitHub Desktop.
UI T/F select which sets a objects timestamp
# 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