instructions 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +20 -0
- data/README.textile +141 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/extras/hint_errors.en.yml +27 -0
- data/init.rb +0 -0
- data/install.rb +1 -0
- data/instructions.gemspec +19 -0
- data/instructions.gemspec.generated +89 -0
- data/lib/core_files.rb +4 -0
- data/lib/instructions.rb +6 -0
- data/lib/instructions/abstract.rb +21 -0
- data/lib/instructions/attribute_state.rb +25 -0
- data/lib/instructions/configuration.rb +49 -0
- data/lib/instructions/core_ext/action_view_ext.rb +9 -0
- data/lib/instructions/error_message.rb +9 -0
- data/lib/instructions/error_message_tag.rb +21 -0
- data/lib/instructions/field_error.rb +33 -0
- data/lib/instructions/field_instructions.rb +6 -0
- data/lib/instructions/form_helper.rb +65 -0
- data/lib/instructions/formatters.rb +10 -0
- data/lib/instructions/instructions_tag.rb +15 -0
- data/lib/instructions/tag.rb +71 -0
- data/lib/instructions/valid_instructions_tag.rb +9 -0
- data/rails/init.rb +0 -0
- data/spec/attribute_state_spec.rb +62 -0
- data/spec/configuration_spec.rb +74 -0
- data/spec/error_message_spec.rb +42 -0
- data/spec/error_message_tag_spec.rb +30 -0
- data/spec/fake_model.rb +17 -0
- data/spec/field_error_spec.rb +85 -0
- data/spec/form_helper_spec.rb +123 -0
- data/spec/formatters_spec.rb +76 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/field_instructions_namespace.rb +2 -0
- data/spec/support/html_safe_string.rb +7 -0
- data/spec/support/rspec_patches.rb +22 -0
- data/spec/tag_spec.rb +136 -0
- data/test/instructions_test.rb +8 -0
- data/test/test_helper.rb +3 -0
- data/uninstall.rb +1 -0
- metadata +110 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module Instructions
|
2
|
+
class Configuration
|
3
|
+
attr_reader :attribute_name_formatters
|
4
|
+
attr_reader :instructions_formatters
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@attribute_name_formatters = {}
|
8
|
+
@instructions_formatters = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.default_message_joiner
|
12
|
+
Proc.new { |messages| messages.join ', ' }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.instance
|
16
|
+
@@instance ||= new
|
17
|
+
end
|
18
|
+
|
19
|
+
def format_attribute_name(attribute_state, &block)
|
20
|
+
@attribute_name_formatters[attribute_state] = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def format_instructions(attribute_state, &block)
|
24
|
+
@instructions_formatters[attribute_state] = block
|
25
|
+
end
|
26
|
+
|
27
|
+
def formatters(attribute_state)
|
28
|
+
{:attribute_name => @attribute_name_formatters[attribute_state],
|
29
|
+
:instructions => @instructions_formatters[attribute_state]}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.formatters(attribute_state)
|
33
|
+
instance.formatters attribute_state
|
34
|
+
end
|
35
|
+
|
36
|
+
def message_joiner(&block)
|
37
|
+
if block_given?
|
38
|
+
@message_joiner = block
|
39
|
+
else
|
40
|
+
@message_joiner ||= self.class.default_message_joiner
|
41
|
+
end
|
42
|
+
@message_joiner
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.message_joiner
|
46
|
+
instance.message_joiner
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'tag')
|
2
|
+
|
3
|
+
module Instructions
|
4
|
+
class ErrorMessageTag
|
5
|
+
include Tag
|
6
|
+
extend ErrorMessage
|
7
|
+
|
8
|
+
def self.build(method, model, model_name, formatters={}, *options)
|
9
|
+
error_message = compose_error_message(method, model)
|
10
|
+
new method, model, model_name, error_message, formatters, *options
|
11
|
+
end
|
12
|
+
|
13
|
+
def descriptor
|
14
|
+
"error_message"
|
15
|
+
end
|
16
|
+
|
17
|
+
def css_class
|
18
|
+
"instructions invalid"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Instructions
|
2
|
+
class FieldError
|
3
|
+
def self.field_error_proc
|
4
|
+
Proc.new do |html_tag, error_data|
|
5
|
+
render_field_with_error(html_tag, error_data).html_safe
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.render_field_with_error(html_tag, error_data)
|
10
|
+
if html_tag =~ /^<label/
|
11
|
+
return wrap_label_tag_with_field_with_errors_label_div(html_tag)
|
12
|
+
end
|
13
|
+
|
14
|
+
model = error_data.object
|
15
|
+
|
16
|
+
model_name = error_data.object_name
|
17
|
+
attribute_name = error_data.method_name
|
18
|
+
human_attribute_name = model.class.human_attribute_name(attribute_name)
|
19
|
+
attribute_errors = model.errors[attribute_name.to_sym]
|
20
|
+
|
21
|
+
field_with_errors_div = wrap_html_tag_with_field_with_errors_div(html_tag)
|
22
|
+
"#{field_with_errors_div}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.wrap_html_tag_with_field_with_errors_div(html_tag)
|
26
|
+
"<div class=\"field_with_errors\">#{html_tag}</div>"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.wrap_label_tag_with_field_with_errors_label_div(div_tag)
|
30
|
+
"<div class=\"field_with_errors_label\">#{div_tag}</div>"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'attribute_state')
|
2
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'formatters')
|
3
|
+
|
4
|
+
module Instructions
|
5
|
+
module FormHelper
|
6
|
+
include AttributeState
|
7
|
+
include Formatters
|
8
|
+
|
9
|
+
def instructions(method, message=nil, options={})
|
10
|
+
text = message_text method, message
|
11
|
+
renderer(method, text, options).render
|
12
|
+
end
|
13
|
+
|
14
|
+
def message_text(method, message)
|
15
|
+
return message if message.is_a? String
|
16
|
+
return active_model_message(method, message) if [Symbol, Hash].include? message.class
|
17
|
+
return active_model_messages(method, message) if message.is_a? Array
|
18
|
+
end
|
19
|
+
|
20
|
+
def active_model_messages(method, message)
|
21
|
+
messages = []
|
22
|
+
generator = generator(@object)
|
23
|
+
message.each do |m|
|
24
|
+
messages << active_model_message(method, m, generator)
|
25
|
+
end
|
26
|
+
Configuration.message_joiner.call messages
|
27
|
+
end
|
28
|
+
|
29
|
+
def active_model_message(method, message, generator=nil)
|
30
|
+
generator ||= generator(@object)
|
31
|
+
if message.is_a? Hash
|
32
|
+
return generator.generate_message(method, message.keys[0], :count => message.values[0])
|
33
|
+
else
|
34
|
+
return generator.generate_message(method, message)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def generator(object)
|
39
|
+
ActiveModel::Errors.new object
|
40
|
+
end
|
41
|
+
|
42
|
+
def test
|
43
|
+
def method_missing(method_name, *args)
|
44
|
+
"<div class=\"method_missing\" style=\"font-weight: bold; color: red;\">No helper found to respond to \"#{method_name}\"</div>"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def renderer(method, text, options={})
|
49
|
+
attribute_state = determine method
|
50
|
+
formatters = merge attribute_state, options
|
51
|
+
|
52
|
+
if attribute_state == :new
|
53
|
+
return InstructionsTag.new method, @object, @object_name, text, formatters
|
54
|
+
end
|
55
|
+
|
56
|
+
if attribute_state == :valid
|
57
|
+
return ValidInstructionsTag.new method, @object, @object_name, text, formatters
|
58
|
+
end
|
59
|
+
|
60
|
+
if attribute_state == :invalid
|
61
|
+
return ErrorMessageTag.build method, @object, @object_name, formatters
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Instructions
|
2
|
+
module Formatters
|
3
|
+
def merge(attribute_state, options)
|
4
|
+
configured_formatters = Configuration.formatters(attribute_state)
|
5
|
+
tag_formatters = (options[:formatters] || {}) [attribute_state] || {}
|
6
|
+
|
7
|
+
configured_formatters.merge tag_formatters
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'abstract')
|
2
|
+
|
3
|
+
module Instructions
|
4
|
+
module Tag
|
5
|
+
include Abstract
|
6
|
+
abstract_method :descriptor
|
7
|
+
abstract_method :css_class
|
8
|
+
|
9
|
+
attr_reader :formatters
|
10
|
+
|
11
|
+
def initialize(method, model, model_name, instructions, formatters={})
|
12
|
+
@method = method
|
13
|
+
@model = model
|
14
|
+
@instructions = instructions
|
15
|
+
@model_name = model_name
|
16
|
+
@formatters = formatters
|
17
|
+
end
|
18
|
+
|
19
|
+
def render
|
20
|
+
"#{open_tag}#{instructions}#{close_tag}".html_safe
|
21
|
+
end
|
22
|
+
|
23
|
+
def instructions
|
24
|
+
return "#{formatted_attribute_name}" if @instructions.nil? or @instructions.empty?
|
25
|
+
"#{formatted_attribute_name} #{formatted_instructions}".strip
|
26
|
+
end
|
27
|
+
|
28
|
+
def formatted_attribute_name
|
29
|
+
return attribute_name_formatter.call(attribute_name) if attribute_name_formatter
|
30
|
+
attribute_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def attribute_name
|
34
|
+
@model.class.human_attribute_name(@method)
|
35
|
+
end
|
36
|
+
|
37
|
+
def attribute_name_formatter
|
38
|
+
@formatters[:attribute_name]
|
39
|
+
end
|
40
|
+
|
41
|
+
def formatted_instructions
|
42
|
+
return instructions_formatter.call(@instructions) if instructions_formatter
|
43
|
+
@instructions
|
44
|
+
end
|
45
|
+
|
46
|
+
def instructions_formatter
|
47
|
+
@formatters[:instructions]
|
48
|
+
end
|
49
|
+
|
50
|
+
def identifier
|
51
|
+
"#{field_identifier}_#{descriptor}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def field_identifier
|
55
|
+
"#{@model_name}_#{@method}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def formatted_css_class
|
59
|
+
return "#{css_class} empty" if instructions.empty?
|
60
|
+
"#{css_class}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def open_tag
|
64
|
+
"<div id=\"#{identifier}\" data-for=\"#{field_identifier}\" class=\"#{formatted_css_class}\">"
|
65
|
+
end
|
66
|
+
|
67
|
+
def close_tag
|
68
|
+
"</div>"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/rails/init.rb
ADDED
File without changes
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AttributeState do
|
4
|
+
before do
|
5
|
+
@model = Object.new
|
6
|
+
|
7
|
+
@model_errors = []
|
8
|
+
@model.stub :errors => @model_errors
|
9
|
+
|
10
|
+
@attribute_state_object = Object.new.extend AttributeState
|
11
|
+
model = @model
|
12
|
+
@attribute_state_object.instance_eval { @object = model }
|
13
|
+
|
14
|
+
setup_model_errors
|
15
|
+
setup_model_state
|
16
|
+
|
17
|
+
@state = @attribute_state_object.determine :some_attribute
|
18
|
+
end
|
19
|
+
|
20
|
+
context "unsaved model" do
|
21
|
+
def setup_model_state
|
22
|
+
@model.stub :new_record? => true
|
23
|
+
end
|
24
|
+
|
25
|
+
context "attribute without a value for an unsaved model" do
|
26
|
+
def setup_model_errors
|
27
|
+
@model.stub :some_attribute => nil
|
28
|
+
@model_errors.stub :[] => []
|
29
|
+
end
|
30
|
+
|
31
|
+
observe "is: new" do
|
32
|
+
@state.should == :new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "saved model" do
|
38
|
+
def setup_model_state
|
39
|
+
@model.stub :new_record? => false
|
40
|
+
end
|
41
|
+
|
42
|
+
context "attribute does not have errors" do
|
43
|
+
def setup_model_errors
|
44
|
+
@model_errors.stub :[] => []
|
45
|
+
end
|
46
|
+
|
47
|
+
observe "is: valid" do
|
48
|
+
@state.should == :valid
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "attribute has errors" do
|
53
|
+
def setup_model_errors
|
54
|
+
@model_errors.stub :[] => ["some error message"]
|
55
|
+
end
|
56
|
+
|
57
|
+
observe "is: invalid" do
|
58
|
+
@state.should == :invalid
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context Instructions, "configuring" do
|
4
|
+
before do
|
5
|
+
Configuration.stub :instance => @configuration
|
6
|
+
|
7
|
+
@config_block_arg = Object.new
|
8
|
+
Instructions.configure do |config|
|
9
|
+
@config_block_arg = config
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
observe "invokes the configuration block" do
|
14
|
+
@config_block_arg.should == @configuration
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe Configuration do
|
19
|
+
before do
|
20
|
+
@proc = Proc.new {}
|
21
|
+
end
|
22
|
+
|
23
|
+
context "configuring an attribute name formatter" do
|
24
|
+
before do
|
25
|
+
subject.format_attribute_name :some_attribute_state, &@proc
|
26
|
+
end
|
27
|
+
|
28
|
+
observe "adds attribute name formatter block to the configured formatters" do
|
29
|
+
subject.attribute_name_formatters[:some_attribute_state].should == @proc
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "configuring an instruction formatter" do
|
34
|
+
before do
|
35
|
+
subject.format_instructions :some_attribute_state, &@proc
|
36
|
+
end
|
37
|
+
|
38
|
+
observe "adds attribute name formatter block to the configured formatters" do
|
39
|
+
subject.instructions_formatters[:some_attribute_state].should == @proc
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "getting formatters for a specific kind" do
|
44
|
+
before do
|
45
|
+
subject.format_attribute_name :some_attribute_state, &@proc
|
46
|
+
subject.format_instructions :some_attribute_state, &@proc
|
47
|
+
end
|
48
|
+
|
49
|
+
observe "gets the attribute name formatter registered for the kind" do
|
50
|
+
subject.formatters(:attribute_name).should_not be_nil
|
51
|
+
end
|
52
|
+
|
53
|
+
observe "gets the instructions formatter registered for the kind" do
|
54
|
+
subject.formatters(:instructions).should_not be_nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "short hand for retrieving formatters (using class methods)" do
|
59
|
+
before do
|
60
|
+
Configuration.stub :instance => subject
|
61
|
+
subject.stub :formatters => nil
|
62
|
+
end
|
63
|
+
|
64
|
+
because { Configuration.formatters :some_attribute_state }
|
65
|
+
|
66
|
+
observe "uses the configuration singleton" do
|
67
|
+
Configuration.should_have_received :instance
|
68
|
+
end
|
69
|
+
|
70
|
+
observe "uses gets the formatters from the configuration singleton" do
|
71
|
+
subject.should_have_received :formatters
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|