semanticgap_date_time_form 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,50 @@
1
+ SemanticGapDateTimeForm
2
+ ========================
3
+
4
+ This provides extends Rails' FormBuilder to have a #datetime_form method
5
+ which is more awesome than the standard #datetime_select.
6
+
7
+
8
+ Dependencies
9
+ =============
10
+
11
+ * ActiveForm
12
+ * git://github.com/zaczheng/activeform.git
13
+ * http://github.com/nayutaya/active_form.git
14
+
15
+
16
+ Installation
17
+ =============
18
+
19
+ $ cd RAILS_ROOT
20
+ $ ./script/plugin install git://github.com/zaczheng/activeform.git
21
+ $ ./script/plugin install http://git.oss.semanticgap.com/ruby/semanticgap_date_time_form.git
22
+
23
+
24
+ Usage
25
+ ======
26
+
27
+ Model
28
+ ------
29
+
30
+ class Record < ActiveRecord::Base
31
+ datetime_fields_for :created_at
32
+ datetime_fields_for :published_at, :allow_blank => true
33
+ end
34
+
35
+ View
36
+ -----
37
+
38
+ <% form_for @record do |f| -%>
39
+ <%= f.label :published_at %>
40
+ <%= f.datetime_form :published_at %>
41
+
42
+ <%= f.label :created_at %>
43
+ <%= f.datetime_form :created_at %>
44
+ <% end -%>
45
+
46
+
47
+ Legal
48
+ ======
49
+
50
+ Copyright (c) 2010 SemanticGap(R), released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ require 'spec/rake/spectask'
9
+
10
+ desc "Run the specs"
11
+ Spec::Rake::SpecTask.new(:spec) do |t|
12
+ t.spec_opts = [ '-fprogress', '-fhtml:doc/spec.html' ]
13
+ t.spec_files = FileList['spec/**/*_spec.rb']
14
+ end
15
+
16
+ namespace :spec do
17
+ desc "Run the specs with rcov"
18
+ Spec::Rake::SpecTask.new(:coverage) do |t|
19
+ t.spec_opts = [ '-fprogress', '-fhtml:doc/spec.html' ]
20
+ t.rcov = true
21
+ t.rcov_opts = [ '--rails', '--exclude', 'spec/*,gems/*' ]
22
+ t.spec_files = FileList['spec/**/*_spec.rb']
23
+ end
24
+ end
25
+
26
+ desc 'Generate documentation for the semanticgap_datetime_fields plugin.'
27
+ Rake::RDocTask.new(:rdoc) do |rdoc|
28
+ rdoc.rdoc_dir = 'rdoc'
29
+ rdoc.title = 'SemanticgapDatetimeFields'
30
+ rdoc.options << '--line-numbers' << '--inline-source'
31
+ rdoc.rdoc_files.include('README')
32
+ rdoc.rdoc_files.include('lib/**/*.rb')
33
+ end
@@ -0,0 +1,6 @@
1
+ require 'semanticgap_date_time_form/form'
2
+ require 'semanticgap_date_time_form/active_record_mixin'
3
+ require 'semanticgap_date_time_form/form_builder_mixin'
4
+
5
+ ActionView::Helpers::FormBuilder.send(:include, SemanticGap::DateTimeForm::FormBuilderMixin)
6
+ ActiveRecord::Base.send(:include, SemanticGap::DateTimeForm::ActiveRecordMixin)
@@ -0,0 +1,42 @@
1
+ module SemanticGap
2
+ module DateTimeForm
3
+ module ActiveRecordMixin
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def datetime_fields_for(attr, options = Hash.new)
10
+ method_name = "#{attr}_fields"
11
+ define_method(method_name) do
12
+ fields = instance_variable_get("@#{method_name}")
13
+ unless fields
14
+ instance_variable_set("@#{method_name}", Form.new(send(attr), options))
15
+
16
+ end
17
+ instance_variable_get("@#{method_name}")
18
+ end
19
+
20
+ define_method("#{method_name}=") do |params|
21
+ fields = send(method_name)
22
+ fields.attributes = params
23
+ send("#{attr}=", fields.to_datetime)
24
+ end
25
+
26
+ define_method("validate_#{method_name}") do
27
+ form = send(method_name)
28
+ errors.add(attr, "is invalid") unless form.valid?
29
+ end
30
+
31
+ validate "validate_#{method_name}"
32
+
33
+ define_method("update_#{attr}") do
34
+ send("#{attr}=", send(method_name).to_datetime) if send(attr).nil?
35
+ end
36
+
37
+ before_save "update_#{attr}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,117 @@
1
+ require 'active_form'
2
+
3
+ module SemanticGap
4
+ module DateTimeForm
5
+ class Form < ActiveForm
6
+ def initialize(*args)
7
+ super()
8
+
9
+ options = args.extract_options!
10
+ date = args.first
11
+
12
+ @time_zone = options.fetch(:time_zone) { Time.zone || ActiveSupport::TimeZone.new('UTC') }
13
+ @allow_blank = options.fetch(:allow_blank) { false }
14
+
15
+ self.date = date if date.respond_to?(:strftime)
16
+ end
17
+
18
+ def date=(d)
19
+ d = time_zone.utc_to_local(d.to_datetime.utc)
20
+
21
+ self.attributes = {
22
+ :year => d.year, :month => d.month, :day => d.day,
23
+ :hour => d.strftime("%I"), :minute => d.strftime("%M"), :ampm => d.strftime("%p")
24
+ }
25
+ end
26
+
27
+ attr_accessor :year, :month, :day, :hour, :minute, :ampm, :time_zone
28
+
29
+ validate :validate_time
30
+ validate :validate_date
31
+
32
+ def month
33
+ if @month.kind_of? Symbol
34
+ @month
35
+ elsif @month
36
+ @month.to_i
37
+ end
38
+ end
39
+
40
+ def time_zone_name
41
+ time_zone.name
42
+ end
43
+
44
+ def time_zone_name=(str)
45
+ tz = ActiveSupport::TimeZone.new(str)
46
+
47
+ if tz
48
+ self.time_zone = tz
49
+ time_zone_name
50
+ end
51
+ end
52
+
53
+ def allow_blank?
54
+ @allow_blank
55
+ end
56
+
57
+ def validate_time
58
+ return if allow_blank? && time_blank?
59
+
60
+ errors.add(:hour, "is invalid") unless hour.to_s =~ /\d+/ && (0..23).include?(hour.to_i)
61
+ errors.add(:minute, "is invalid") unless minute.to_s =~ /\d+/ && (0..59).include?(minute.to_i)
62
+ errors.add(:ampm, "must be AM or PM") unless %W(AM PM am pm).include?(ampm)
63
+ end
64
+
65
+ def validate_date
66
+ return if allow_blank? && date_blank?
67
+
68
+ errors.add(:year, "#{year} is not a valid year") unless year.to_s =~ /\d+/
69
+ errors.add(:month, "#{month} is not a valid month") unless (1..12).include?(month.to_i)
70
+ errors.add(:day, "#{day} is not a valid day") unless day.to_s =~ /\d+/ && (1..31).include?(day.to_i)
71
+
72
+ if errors[:month].blank? && errors[:month].blank?
73
+ begin
74
+ build_datetime
75
+ rescue ArgumentError
76
+ errors.add(:day, "is invalid")
77
+ end
78
+ end
79
+ end
80
+
81
+ def pm?
82
+ ampm[0, 1].downcase == 'p' unless ampm.blank?
83
+ end
84
+
85
+ def blank?
86
+ time_blank? && date_blank?
87
+ end
88
+
89
+ def to_datetime
90
+ return nil if (allow_blank? && blank?) || !valid?
91
+
92
+ time_zone.local_to_utc(build_datetime)
93
+ end
94
+
95
+ private
96
+
97
+ def build_datetime
98
+ DateTime.new(year.to_i, month, day.to_i, adjusted_hour, minute.to_i)
99
+ end
100
+
101
+ def adjusted_hour
102
+ h = hour.to_i
103
+ h = h + 12 if h < 12 && pm?
104
+ h = 0 if h == 12 && !pm?
105
+ h
106
+ end
107
+
108
+ def time_blank?
109
+ hour.blank? && minute.blank?
110
+ end
111
+
112
+ def date_blank?
113
+ year.blank? && @month.blank? && day.blank?
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,26 @@
1
+ module SemanticGap
2
+ module DateTimeForm
3
+ module FormBuilderMixin
4
+ def datetime_form(attr, value = nil)
5
+ value ||= @object.try(datetime_param(attr))
6
+ form = fields_for(datetime_param(attr), value) do |f|
7
+ f.text_field(:year, :size => 4) + "/" +
8
+ @template.select_month(value, :prefix => f.object_name, :include_blank => value.allow_blank?) + "/" +
9
+ f.text_field(:day, :size => 2) + "&mdash;" +
10
+ f.text_field(:hour, :size => 2) + ":" +
11
+ f.text_field(:minute, :size => 2) +
12
+ f.select(:ampm, %W(AM PM)) +
13
+ @template.content_tag('span', value.time_zone.name, :class => 'timezone')
14
+ # @template.time_zone_select(f.object_name, :time_zone_name, ActiveSupport::TimeZone.us_zones, :default => Time.zone.name)
15
+ end
16
+
17
+ @template.content_tag('div', form, :class => 'sg-datetime-form')
18
+ end
19
+
20
+ private
21
+ def datetime_param(attr)
22
+ "#{attr}_fields"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,127 @@
1
+ module SemanticGap
2
+ module DateTimeForm
3
+ module Spec
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def it_has_datetime_fields_for(attr, options = Hash.new)
10
+ attr_fields = "#{attr}_fields"
11
+
12
+ describe "\##{attr_fields}" do
13
+ context 'first call' do
14
+ before(:each) do
15
+ subject.send("#{attr}=", Time.now)
16
+ end
17
+
18
+ it "creates a datetime form initialized with the value @#{attr} and the options #{options.inspect}" do
19
+ SemanticGap::DateTimeForm::Form.should_receive(:new).
20
+ with(subject.send(attr), options)
21
+
22
+ subject.send(attr_fields)
23
+ end
24
+
25
+ it "returns the form" do
26
+ SemanticGap::DateTimeForm::Form.should_receive(:new).and_return(:form)
27
+ subject.send(attr_fields).should == :form
28
+ end
29
+ end
30
+
31
+ context 'subsequent calls' do
32
+ before(:each) do
33
+ @form = subject.send(attr_fields)
34
+ end
35
+
36
+ it "does not create a new datetime form" do
37
+ SemanticGap::DateTimeForm::Form.should_not_receive(:new)
38
+ subject.send(attr_fields)
39
+ end
40
+
41
+ it "returns the form" do
42
+ subject.send(attr_fields).should == @form
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "\##{attr_fields}=" do
48
+ it "updates the fields' attributes with the value" do
49
+ @params = { :year => 2010, :month => 5, :day => 6 }
50
+ subject.send(attr_fields).should_receive(:attributes=).with(@params)
51
+
52
+ subject.send("#{attr_fields}=", @params)
53
+ end
54
+
55
+ context 'with valid fields' do
56
+ it "updates @#{attr} to the fields' datetime" do
57
+ @params = { :year => 2010, :month => 5, :day => 6, :hour => 12, :minute => 0, :ampm => 'PM' }
58
+
59
+ lambda { subject.send("#{attr_fields}=", @params) }.
60
+ should change(subject, attr).to(DateTime.new(2010, 5, 6, 12, 0))
61
+ end
62
+ end
63
+
64
+ context 'with invalid fields' do
65
+ before(:each) do
66
+ subject.send("#{attr}=", Time.now)
67
+ end
68
+
69
+ it "updates @#{attr} to nil" do
70
+ lambda { subject.send("#{attr_fields}=", { :month => 13 } ) }.
71
+ should change(subject, attr).to(nil)
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#valid?' do
77
+ context 'when #{attr_fields} are valid' do
78
+ before(:each) do
79
+ subject.send(attr_fields).stub!(:valid?).and_return(true)
80
+ end
81
+
82
+ it "does NOT add errors for #{attr}" do
83
+ subject.should_not have(:any).errors_on(attr)
84
+ end
85
+ end
86
+
87
+ context 'when #{attr_fields} are invalid' do
88
+ before(:each) do
89
+ subject.send(attr_fields).stub!(:valid?).and_return(false)
90
+ end
91
+
92
+ it "adds errors for :published_at" do
93
+ subject.should have_at_least(1).error_on(attr)
94
+ end
95
+ end
96
+ end
97
+
98
+ describe '#save!' do
99
+ context "when #{attr} is nil" do
100
+ before(:each) do
101
+ subject.send("#{attr}=", nil)
102
+ end
103
+
104
+ it "updates #{attr} with the #{attr_fields} datetime" do
105
+ @time = DateTime.now
106
+ subject.send(attr_fields).should_receive(:to_datetime).and_return(@time)
107
+ subject.send(attr_fields).stub!(:valid?).and_return(true)
108
+
109
+ lambda { subject.save! }.should change(subject, attr).to(@time)
110
+ end
111
+ end
112
+
113
+ context "when #{attr} is not nil" do
114
+ before(:each) do
115
+ subject.send("#{attr}=", Time.now)
116
+ end
117
+
118
+ it "does NOT change #{attr}" do
119
+ lambda { subject.save! }.should_not change(subject, attr)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,3 @@
1
+ .sg-datetime-form > DIV {
2
+ display: inline;
3
+ }
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'semanticgap_date_time_form'
data/rails/install.rb ADDED
@@ -0,0 +1,8 @@
1
+ # Install hook code here
2
+ File.copy("public/stylesheets/semanticgap_date_time_form.css",
3
+ File.join(Rails.root, "public", "stylesheets", "semanticgap_date_time_form.css"))
4
+
5
+ puts
6
+ puts "Remember to add the following to your layout:"
7
+ puts " <%= stylesheet_link_tag 'semanticgap_date_time_form' %>"
8
+ puts