instructions 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +9 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +26 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.textile +141 -0
  6. data/Rakefile +40 -0
  7. data/VERSION +1 -0
  8. data/extras/hint_errors.en.yml +27 -0
  9. data/init.rb +0 -0
  10. data/install.rb +1 -0
  11. data/instructions.gemspec +19 -0
  12. data/instructions.gemspec.generated +89 -0
  13. data/lib/core_files.rb +4 -0
  14. data/lib/instructions.rb +6 -0
  15. data/lib/instructions/abstract.rb +21 -0
  16. data/lib/instructions/attribute_state.rb +25 -0
  17. data/lib/instructions/configuration.rb +49 -0
  18. data/lib/instructions/core_ext/action_view_ext.rb +9 -0
  19. data/lib/instructions/error_message.rb +9 -0
  20. data/lib/instructions/error_message_tag.rb +21 -0
  21. data/lib/instructions/field_error.rb +33 -0
  22. data/lib/instructions/field_instructions.rb +6 -0
  23. data/lib/instructions/form_helper.rb +65 -0
  24. data/lib/instructions/formatters.rb +10 -0
  25. data/lib/instructions/instructions_tag.rb +15 -0
  26. data/lib/instructions/tag.rb +71 -0
  27. data/lib/instructions/valid_instructions_tag.rb +9 -0
  28. data/rails/init.rb +0 -0
  29. data/spec/attribute_state_spec.rb +62 -0
  30. data/spec/configuration_spec.rb +74 -0
  31. data/spec/error_message_spec.rb +42 -0
  32. data/spec/error_message_tag_spec.rb +30 -0
  33. data/spec/fake_model.rb +17 -0
  34. data/spec/field_error_spec.rb +85 -0
  35. data/spec/form_helper_spec.rb +123 -0
  36. data/spec/formatters_spec.rb +76 -0
  37. data/spec/spec_helper.rb +7 -0
  38. data/spec/support/field_instructions_namespace.rb +2 -0
  39. data/spec/support/html_safe_string.rb +7 -0
  40. data/spec/support/rspec_patches.rb +22 -0
  41. data/spec/tag_spec.rb +136 -0
  42. data/test/instructions_test.rb +8 -0
  43. data/test/test_helper.rb +3 -0
  44. data/uninstall.rb +1 -0
  45. 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,9 @@
1
+ ActionView::Base.field_error_proc = Instructions::FieldError.field_error_proc
2
+
3
+ ActionView::Base.default_form_builder.class_eval do
4
+ include Instructions::FormHelper
5
+ end
6
+
7
+ ActionView::Base.instance_eval do
8
+ include Instructions::FormHelper
9
+ end
@@ -0,0 +1,9 @@
1
+ module Instructions
2
+ module ErrorMessage
3
+ def compose_error_message(method, model)
4
+ errors = model.errors[method]
5
+ return Configuration.message_joiner.call errors if errors.kind_of? Array
6
+ return errors if errors.kind_of? String
7
+ end
8
+ end
9
+ 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,6 @@
1
+ module Instructions
2
+ def self.configure(&block)
3
+ configuration = Configuration.instance
4
+ block.call configuration
5
+ end
6
+ 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,15 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'tag')
2
+
3
+ module Instructions
4
+ class InstructionsTag
5
+ include Tag
6
+
7
+ def descriptor
8
+ "instructions"
9
+ end
10
+
11
+ def css_class
12
+ "#{descriptor}"
13
+ end
14
+ end
15
+ 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
@@ -0,0 +1,9 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'tag')
2
+
3
+ module Instructions
4
+ class ValidInstructionsTag < InstructionsTag
5
+ def css_class
6
+ "instructions valid"
7
+ end
8
+ end
9
+ end
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