instructions 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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