representations 0.0.3 → 0.0.4
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.
- data/README.markdown +21 -3
- data/Rakefile +3 -4
- data/VERSION +1 -1
- data/init.rb +0 -2
- data/lib/active_record_representation.rb +60 -0
- data/lib/associations_representation.rb +60 -0
- data/lib/controller_helpers.rb +10 -0
- data/lib/date_representation.rb +19 -0
- data/lib/default_representation.rb +71 -0
- data/lib/representation.rb +96 -0
- data/lib/representations.rb +23 -256
- data/lib/view_helpers.rb +30 -11
- data/representations.gemspec +8 -2
- metadata +8 -2
data/README.markdown
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
Rails helpers, including form builders are great to allow rapid development of applications and views.
|
4
4
|
|
5
|
-
|
5
|
+
On the other hand they are procedural in nature and have hard time to adapt to complex models. They also live in a single namespace making it difficult to find which helpers apply to which models.
|
6
6
|
|
7
|
-
Representations change syntax to object oriented and model specific.
|
7
|
+
Representations do a paradigm switch and change syntax to object oriented and model specific.
|
8
8
|
|
9
9
|
## Example usage
|
10
10
|
|
@@ -25,7 +25,7 @@ Rails helpers:
|
|
25
25
|
= f.text_field(:last_name)
|
26
26
|
= f.radio_button(:eye_color, 'blue')
|
27
27
|
= f.label(:eye_color_blue, "Blue")
|
28
|
-
= f.submit("
|
28
|
+
= f.submit("ok")
|
29
29
|
|
30
30
|
Representations:
|
31
31
|
|
@@ -42,24 +42,42 @@ Representations:
|
|
42
42
|
= p.last_name.text_field
|
43
43
|
= p.eye_color.radio_button('blue')
|
44
44
|
= p.eye_color.radio_button_label('blue', 'Blue')
|
45
|
+
|
45
46
|
##Extensions
|
47
|
+
|
46
48
|
Representations can be altered. For example to add new method DefaultRepresentation create file app/representations/default_representation.rb with the content:
|
47
49
|
module DefaultRepresentation
|
48
50
|
def new_method
|
49
51
|
some code
|
50
52
|
end
|
51
53
|
end
|
54
|
+
|
52
55
|
##Nested attributes
|
56
|
+
|
53
57
|
- user.children.each do |child|
|
54
58
|
= child.name.label
|
55
59
|
= child.name.text_field
|
56
60
|
= child.delete_checkbox
|
57
61
|
= child.delete_checkbox_label
|
62
|
+
|
58
63
|
Or even:
|
64
|
+
|
59
65
|
- user.children.build do |child|
|
60
66
|
= child.name.label
|
61
67
|
= child.name.text_field
|
68
|
+
|
62
69
|
## Automatic wrapping
|
70
|
+
|
63
71
|
Create file config/initializers/representations.rb
|
72
|
+
|
64
73
|
Representations.enable_automatic_wrapping = true
|
74
|
+
|
65
75
|
if you want Representations to automatically wrap variables from the controller
|
76
|
+
|
77
|
+
##Notes
|
78
|
+
|
79
|
+
Use id method for named routes and helpers generated by `map :resource` for example
|
80
|
+
|
81
|
+
<%@users.each do |u|%>
|
82
|
+
<%= link_to "edit user", edit_user_path(u.id) %>
|
83
|
+
<%end%>
|
data/Rakefile
CHANGED
@@ -19,8 +19,7 @@ Resource representations change syntax to object oriented and model specific."
|
|
19
19
|
"Rakefile",
|
20
20
|
"VERSION",
|
21
21
|
"init.rb",
|
22
|
-
"lib
|
23
|
-
"lib/view_helpers.rb",
|
22
|
+
"lib/*",
|
24
23
|
"rails/init.rb",
|
25
24
|
"representations.gemspec"
|
26
25
|
]
|
@@ -34,12 +33,12 @@ end
|
|
34
33
|
require 'spec/rake/spectask'
|
35
34
|
Spec::Rake::SpecTask.new(:spec) do |spec|
|
36
35
|
spec.libs << 'lib' << 'spec'
|
37
|
-
spec.spec_files = FileList['spec/test_application/spec/
|
36
|
+
spec.spec_files = FileList['spec/test_application/spec/representations/*_spec.rb']
|
38
37
|
end
|
39
38
|
|
40
39
|
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
41
40
|
spec.libs << 'lib' << 'spec'
|
42
|
-
spec.pattern = 'spec/test_application/spec/
|
41
|
+
spec.pattern = 'spec/test_application/spec/representations/*_spec.rb'
|
43
42
|
spec.rcov = true
|
44
43
|
end
|
45
44
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.4
|
data/init.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Representations
|
2
|
+
#Representation for ActiveRecord::Base objects
|
3
|
+
class ActiveRecordRepresentation < Representation
|
4
|
+
#Render partial with the given name and given namespace as a parameter
|
5
|
+
def partial(partial_name, namespace = nil)
|
6
|
+
namespace = get_namespace unless namespace
|
7
|
+
namespace += '/'
|
8
|
+
path = @name.pluralize
|
9
|
+
path = namespace + path
|
10
|
+
path.downcase!
|
11
|
+
@template.render(:partial => "#{path}/#{partial_name}")
|
12
|
+
end
|
13
|
+
#Render partial if it has 'has_one' association with the other model, otherwise do normal to_s
|
14
|
+
def to_s
|
15
|
+
@parent && @parent.instance_variable_get(:@value).class.reflections[:"#{@name}"].macro == :has_one ? partial(@name) : super
|
16
|
+
end
|
17
|
+
#Form tag, namespace depends on the namespace of the controller.
|
18
|
+
def form(&block)
|
19
|
+
raise "You need to provide block to form representation" unless block_given?
|
20
|
+
namespace = get_namespace
|
21
|
+
namespace = '/' + namespace unless namespace.blank?
|
22
|
+
path = namespace + '/' + @name.pluralize
|
23
|
+
path.downcase!
|
24
|
+
content = @template.capture(self, &block)
|
25
|
+
if @value.new_record?
|
26
|
+
@template.concat(@template.form_tag(path, :method => "post"))
|
27
|
+
else
|
28
|
+
path += '/' + "#{@value.id}"
|
29
|
+
@template.concat(@template.form_tag(path, :method => "put"))
|
30
|
+
end
|
31
|
+
@template.concat(content)
|
32
|
+
@template.concat(@template.submit_tag("ok"))
|
33
|
+
@template.concat("</form>")
|
34
|
+
self
|
35
|
+
end
|
36
|
+
#Forwards ActiveRecord invocation and wraps result in appropriate Representation
|
37
|
+
#Suppose that User extends ActiveRecord::Base:
|
38
|
+
#ar_user = User.new
|
39
|
+
#ar_user.nick = 'foo'
|
40
|
+
#user = r(ar_user) #user is now ActiveRecordRepresentation
|
41
|
+
#user.nick.text_field #method_missing will be called on user with method_name = 'nick' in which new method for user will be created and will be called. The newly created method will create a new DefaultRepresentation with @value set to the string 'foo'. Next the text_field will be called on the newly created DefaultRepresentation
|
42
|
+
def method_missing(method_name, *args, &block)
|
43
|
+
method = <<-EOF
|
44
|
+
def #{method_name}(*args, &block)
|
45
|
+
@__#{method_name} ||= Representations.representation_for(@value.#{method_name}, @template, "#{method_name}", self)
|
46
|
+
@__#{method_name}.with_block(&block)
|
47
|
+
@__#{method_name} if block.nil?
|
48
|
+
end
|
49
|
+
EOF
|
50
|
+
::Representations::ActiveRecordRepresentation.class_eval(method, __FILE__, __LINE__)
|
51
|
+
self.__send__(method_name, &block)
|
52
|
+
end
|
53
|
+
private
|
54
|
+
#Gets path to namespace (i.e. if controller is Good::Bad::Ugly::UsersController the R is in the namespace good/bad/ugly)
|
55
|
+
def get_namespace
|
56
|
+
namespace = @template.controller.class.parent_name.split('::') rescue []
|
57
|
+
namespace = namespace.join('/')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Representations
|
2
|
+
#Representation for Collections
|
3
|
+
class AssociationsRepresentation < Representation
|
4
|
+
#initilize @num variable
|
5
|
+
def initialize(object, template, name, parent)
|
6
|
+
super
|
7
|
+
@num = -1 #-1 so first call to num will result in 0
|
8
|
+
end
|
9
|
+
#Creates Representation for every object in the Array and invokes passed block with this Representation as the argument
|
10
|
+
def each
|
11
|
+
@value.each_index do |idx|
|
12
|
+
representation_object = Representations.representation_for(@value[idx], @template, idx.to_s, self)
|
13
|
+
#add to page hidden input with id of the object in the collection
|
14
|
+
tree = representation_object.get_parents_tree
|
15
|
+
name = get_html_name_attribute_value(tree)
|
16
|
+
name << '[id]'
|
17
|
+
tags = get_tags({}, {:value => @value[idx].id.to_s, :name=>name})
|
18
|
+
@template.concat("<input type='hidden' #{tags}/>")
|
19
|
+
yield representation_object
|
20
|
+
end
|
21
|
+
end
|
22
|
+
#Creates new object in the collection and input fields for it defined in the passed block
|
23
|
+
def build
|
24
|
+
new_object = @value.build
|
25
|
+
representation_object = AssociationsRepresentation::NewRecordRepresentation.new(new_object, @template, 'new_' + num.to_s, self)
|
26
|
+
yield representation_object
|
27
|
+
end
|
28
|
+
private
|
29
|
+
attr_reader :num
|
30
|
+
#Used for generating unique @name for ArrayRepresentation::NewRecordRepresentation
|
31
|
+
def num
|
32
|
+
@num += 1
|
33
|
+
end
|
34
|
+
#Representation that wraps newly created ActiveRecord::Base that will be added to some collection
|
35
|
+
class NewRecordRepresentation < Representation
|
36
|
+
#Creates new method which wraps call for ActionRecord
|
37
|
+
#New method returns Representation which represents datatype in the appropriate column
|
38
|
+
def method_missing(method_name_symbol, *args, &block)
|
39
|
+
method_name = method_name_symbol.to_s
|
40
|
+
representation_class = case @value.class.columns_hash[method_name].type
|
41
|
+
when :date
|
42
|
+
Representations::DateRepresentation
|
43
|
+
when :datetime
|
44
|
+
Representations::DateRepresentation
|
45
|
+
else
|
46
|
+
Representations::DefaultRepresentation
|
47
|
+
end
|
48
|
+
method = <<-EOF
|
49
|
+
def #{method_name}(*args, &block)
|
50
|
+
@__#{method_name} ||= #{representation_class}.new(@value.#{method_name}, @template, "#{method_name}", self)
|
51
|
+
@__#{method_name}.with_block(&block)
|
52
|
+
@__#{method_name} if block.nil?
|
53
|
+
end
|
54
|
+
EOF
|
55
|
+
::Representations::AssociationsRepresentation::NewRecordRepresentation.class_eval(method, __FILE__, __LINE__)
|
56
|
+
self.__send__(method_name_symbol, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Representations
|
2
|
+
#unused
|
3
|
+
module ControllerHelpers
|
4
|
+
def uses_representations(hash)
|
5
|
+
raise 'Ambiguous options' if hash.keys.size > 1
|
6
|
+
raise 'Automatic wrapping is disabled' unless Representations.automatic_wrapping
|
7
|
+
@use_r_for = hash[:only] rescue actions.collect{|a| a if hash[:except].include?(a).compact}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Representations
|
2
|
+
#Representation for Date object
|
3
|
+
class DateRepresentation < Representation
|
4
|
+
def select(passed_options = {}, html_options = {})
|
5
|
+
tree = get_parents_tree
|
6
|
+
names = get_html_name_attribute_value(tree)
|
7
|
+
names.pop
|
8
|
+
options = { :object => @parent.instance_variable_get(:@value) }
|
9
|
+
options.merge!(passed_options)
|
10
|
+
@template.date_select(names, @name, options, html_options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
#Something like aliases
|
14
|
+
class TimeWithZoneRepresentation < DateRepresentation
|
15
|
+
end
|
16
|
+
class DateTimeRepresentation < DateRepresentation
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Representations
|
2
|
+
class DefaultRepresentation < Representation
|
3
|
+
#not tested in the view
|
4
|
+
#Returns string with html check box tag
|
5
|
+
def check_box(checked_value = "1", unchecked_value = "0", html_options = {})
|
6
|
+
tree = get_parents_tree
|
7
|
+
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
8
|
+
name_attr_value = get_html_name_attribute_value(tree)
|
9
|
+
tags = get_tags(html_options, {:value => checked_value, :id => id_attr_value, :name=>name_attr_value})
|
10
|
+
%Q{<input type="checkbox" #{tags}/>\n<input type="hidden" value="#{unchecked_value}" id="#{id_attr_value}" name="#{name_attr_value}"/>}
|
11
|
+
end
|
12
|
+
#not tested in the view
|
13
|
+
#Returns string with html file field tag
|
14
|
+
def file_field(html_options = {})
|
15
|
+
tree = get_parents_tree
|
16
|
+
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
17
|
+
tags = get_tags(html_options, {:value => to_s, :id => id_attr_value, :name=>get_html_name_attribute_value(tree)})
|
18
|
+
%Q{<input type="file" #{tags}/>}
|
19
|
+
end
|
20
|
+
#not tested in the view
|
21
|
+
#Returns string with html hidden input tag
|
22
|
+
def hidden_field(html_options = {})
|
23
|
+
tree = get_parents_tree
|
24
|
+
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
25
|
+
tags = get_tags(html_options, {:value => to_s, :id => id_attr_value, :name=>get_html_name_attribute_value(tree)})
|
26
|
+
%Q{<input type="hidden" #{tags}/>}
|
27
|
+
end
|
28
|
+
#Returns string with html text input tag
|
29
|
+
def text_field(html_options = {})
|
30
|
+
tree = get_parents_tree
|
31
|
+
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
32
|
+
tags = get_tags(html_options, {:value => to_s, :id => id_attr_value, :name=>get_html_name_attribute_value(tree)})
|
33
|
+
%Q{<input type="text" #{tags}/>}
|
34
|
+
end
|
35
|
+
#Returns string with html text area tag
|
36
|
+
def text_area(html_options = {})
|
37
|
+
tree = get_parents_tree
|
38
|
+
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
39
|
+
tags = get_tags(html_options, {:id => id_attr_value, :name => get_html_name_attribute_value(tree)})
|
40
|
+
%Q{<textarea #{tags}>\n#{to_s}\n</textarea>}
|
41
|
+
end
|
42
|
+
#Returns string with html password tag
|
43
|
+
def password_field(html_options = {})
|
44
|
+
tree = get_parents_tree
|
45
|
+
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
46
|
+
tags = get_tags(html_options, {:value => to_s, :id => id_attr_value, :name=>get_html_name_attribute_value(tree)})
|
47
|
+
%Q{<input type="password" #{tags}/>}
|
48
|
+
end
|
49
|
+
#Returns string with html radio button tag
|
50
|
+
def radio_button(value, html_options = {})
|
51
|
+
tree = get_parents_tree
|
52
|
+
id_attr_value = tree.collect{ |x| x[0] }.join('_') + "_#{value}"
|
53
|
+
name_attr_value = get_html_name_attribute_value(tree)
|
54
|
+
if @value && @value.capitalize==value.capitalize #if editing existing record and values do match
|
55
|
+
options = {:name => name_attr_value, :value=>value, :id=>id_attr_value, :checked=>"true"}
|
56
|
+
else
|
57
|
+
options = {:name => name_attr_value, :value=>value, :id=>id_attr_value, :checked=>"false"}
|
58
|
+
end
|
59
|
+
tags = get_tags(html_options, options)
|
60
|
+
%Q{<input type="radio" #{tags}/>}
|
61
|
+
end
|
62
|
+
#Returns string with html label tag with 'for' attribute set to the radio button of this object
|
63
|
+
def radio_button_label(radio_button_value, value = nil, html_options = {})
|
64
|
+
tree = get_parents_tree
|
65
|
+
for_attr_value = tree.collect{ |x| x[0] }.join('_') + "_#{radio_button_value}"
|
66
|
+
value = radio_button_value.capitalize if value.nil?
|
67
|
+
tags = get_tags(html_options, {:for => for_attr_value})
|
68
|
+
%Q{<label #{tags}>#{ERB::Util::h(value)}</label>}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Representations
|
2
|
+
class Representation
|
3
|
+
#value - object for which the representation is created
|
4
|
+
#template - template view (needed because some ActionView::Base methods are private)
|
5
|
+
#name - the actuall name of the method that was called on the object's parent that is being initialize
|
6
|
+
#parent - Representation object which contains the object that is being initialize
|
7
|
+
def initialize(value, template, name, parent=nil)
|
8
|
+
@value = value
|
9
|
+
@name = name
|
10
|
+
@template = template
|
11
|
+
@parent = parent
|
12
|
+
|
13
|
+
#extend class if user provided appropriate file (look at the files app/representations/*_representation.rb)
|
14
|
+
#first check if file exists in app/representations
|
15
|
+
file_name = "#{RAILS_ROOT}/app/representations/#{send(:class).to_s.demodulize.tableize.singularize}.rb"
|
16
|
+
if File.exist?(file_name)
|
17
|
+
ActiveSupport::Dependencies.require_or_load(file_name)
|
18
|
+
Rails.logger.info "Extending Representation ::#{self.class.to_s.demodulize}"
|
19
|
+
self.class.send(:include, "::#{self.class.to_s.demodulize}".constantize)
|
20
|
+
end
|
21
|
+
#extend this object's class if user provided per-model extensions (i.e. for Job model look at app/representations/job_representation.rb)
|
22
|
+
file_name = "#{RAILS_ROOT}/app/representations/#{value.class.to_s.demodulize.tableize.singularize}_representation.rb"
|
23
|
+
if File.exist?(file_name)
|
24
|
+
ActiveSupport::Dependencies.require_or_load(file_name)
|
25
|
+
Rails.logger.info "Extending Representation ::#{self.class.to_s.demodulize} for model #{value.class.to_s}"
|
26
|
+
send(:extend, "::#{value.class.to_s}Representation".constantize)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
def +(arg)
|
30
|
+
to_s + arg.to_s
|
31
|
+
end
|
32
|
+
def id
|
33
|
+
@value.id if @value
|
34
|
+
end
|
35
|
+
#returns escaped string from the object's to_s method
|
36
|
+
def to_s
|
37
|
+
@value ? ERB::Util::h(@value.to_s) : ''
|
38
|
+
end
|
39
|
+
#returns html label tag for the representation
|
40
|
+
def label(value = nil, html_options = {})
|
41
|
+
tree = get_parents_tree
|
42
|
+
for_attr_value = tree.collect{ |x| x[0] }.join('_')
|
43
|
+
tags = get_tags(html_options, {:for => for_attr_value})
|
44
|
+
value = ERB::Util::h(@name.humanize) if value.nil?
|
45
|
+
%Q{<label #{tags}>#{value}</label>}
|
46
|
+
end
|
47
|
+
protected
|
48
|
+
#Call the passed block (if any)
|
49
|
+
def with_block(&block)
|
50
|
+
yield self if block_given? && @value
|
51
|
+
end
|
52
|
+
#Returns two dimensional array based on the tree of the Represantation objects which are linked together by the @parent field
|
53
|
+
#First element of the array consists of Representation's @name and the second of Representation's class
|
54
|
+
def get_parents_tree
|
55
|
+
tree = Array.new
|
56
|
+
tree[0] = []
|
57
|
+
tree[0][0] = @name
|
58
|
+
tree[0][1] = self.class
|
59
|
+
parent = @parent
|
60
|
+
while parent do #iterate parent tree
|
61
|
+
array = []
|
62
|
+
array[0] = parent.instance_variable_get(:@name)
|
63
|
+
array[1] = parent.class
|
64
|
+
tree.unshift(array)
|
65
|
+
parent = parent.instance_variable_get(:@parent)
|
66
|
+
end
|
67
|
+
tree #tree now looks something like this [['user', ActiverRecordRepresentation], ['nick', DefaultRepresentation]]
|
68
|
+
end
|
69
|
+
#Creates value of the html name attribute according to the passed tree
|
70
|
+
def get_html_name_attribute_value(tree)
|
71
|
+
first = tree.delete_at(0)
|
72
|
+
root_name = first[0]
|
73
|
+
name = []
|
74
|
+
prev = nil
|
75
|
+
tree.each do |elem|
|
76
|
+
if elem[1] == DefaultRepresentation || elem[1] == TimeWithZoneRepresentation || prev == AssociationsRepresentation
|
77
|
+
name.push "[" + elem[0] + "]"
|
78
|
+
else
|
79
|
+
name.push "[" + elem[0] + "_attributes]"
|
80
|
+
end
|
81
|
+
prev = elem[1]
|
82
|
+
end
|
83
|
+
name.unshift(root_name)
|
84
|
+
end
|
85
|
+
#Returns string created by merging two hashes of html options passed as the arguments
|
86
|
+
def get_tags(user_options, base_options)
|
87
|
+
options = base_options.merge(user_options)
|
88
|
+
options.stringify_keys!
|
89
|
+
options = options.sort
|
90
|
+
options.map{ |key, value| %(#{key}="#{value}" ) }
|
91
|
+
end
|
92
|
+
def method_missing(method_name)
|
93
|
+
@value ? super : self
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/representations.rb
CHANGED
@@ -1,25 +1,38 @@
|
|
1
|
+
require 'representation.rb'
|
2
|
+
require 'default_representation.rb'
|
3
|
+
require 'associations_representation.rb'
|
4
|
+
require 'active_record_representation.rb'
|
5
|
+
require 'date_representation.rb'
|
1
6
|
module Representations
|
7
|
+
|
8
|
+
#Currently this method is never called but maybe someday it will have to be :-)
|
9
|
+
#Changes ActionController::PolymorphicRoutes#polymorphic_path so it can handle R
|
10
|
+
def self.eval_polymorphic_routes
|
11
|
+
ActionController::PolymorphicRoutes.class_eval do
|
12
|
+
def polymorphic_path_with_r(object, options = {})
|
13
|
+
object.is_a?(Representation) ? polymorphic_path_without_r(object.instance_variable_get(:@value), options) : polymorphic_path_without_r(object, options)
|
14
|
+
end
|
15
|
+
alias_method_chain :polymorphic_path, :r
|
16
|
+
end
|
17
|
+
end
|
2
18
|
#Enables automatic wrapping
|
3
|
-
#
|
19
|
+
#Currently there's no way of disabling it
|
4
20
|
def self.enable_automatic_wrapping=(value)
|
5
21
|
if value
|
6
|
-
ActionView::Base.class_eval
|
7
|
-
def instance_variable_set_with_r(symbol, obj)
|
8
|
-
load ActiveSupport::Dependencies.search_for_file('representations.rb')
|
9
|
-
obj = Representations.representation_for(obj, self, symbol.to_s[1..-1]) if obj.is_a?(ActiveRecord::Base)
|
10
|
-
instance_variable_set_without_r(symbol, obj) #call to the original method
|
11
|
-
end
|
12
|
-
self.alias_method_chain :instance_variable_set, :r
|
13
|
-
end
|
22
|
+
ActionView::Base.class_eval{ self.alias_method_chain :instance_variable_set, :r }
|
14
23
|
end
|
15
24
|
end
|
16
25
|
#Creates Representation for object passed as a paremeter, type of the representation
|
17
26
|
#depends on the type of the object
|
18
|
-
def representation_for(object, template, name
|
27
|
+
def representation_for(object, template, name, parent=nil)
|
19
28
|
representation_class =
|
20
29
|
begin
|
21
30
|
if object.is_a?(ActiveRecord::Base)
|
22
31
|
ActiveRecordRepresentation
|
32
|
+
elsif parent && parent.instance_variable_get(:@value).class.reflections[name.to_sym] && parent.instance_variable_get(:@value).class.reflections[name.to_sym].macro == :has_one
|
33
|
+
parent.instance_variable_get(:@value).send(name+'=', parent.instance_variable_get(:@value).class.reflections[name.to_sym].klass.new) if parent.instance_variable_get(:@value).send(name).nil? #create new AR object
|
34
|
+
object = parent.instance_variable_get(:@value).send(name)
|
35
|
+
Representations::ActiveRecordRepresentation
|
23
36
|
else
|
24
37
|
"Representations::#{object.class.to_s.demodulize}Representation".constantize
|
25
38
|
end
|
@@ -30,251 +43,5 @@ module Representations
|
|
30
43
|
end
|
31
44
|
|
32
45
|
module_function :representation_for
|
33
|
-
class Representation
|
34
|
-
|
35
|
-
#value - object for which the representation is created
|
36
|
-
#template - template view (needed because some ActionView::Base methods are private)
|
37
|
-
#name - the actuall name of the method that was called on the object's parent that is being initialize
|
38
|
-
#parent - Representation object which contains the object that is being initialize
|
39
|
-
def initialize(value, template, name=nil, parent=nil)
|
40
|
-
@value = value
|
41
|
-
@name = name
|
42
|
-
@template = template
|
43
|
-
@parent = parent
|
44
|
-
#extend class if user provided appropriate file (look at the files app/representations/*_representation.rb)
|
45
|
-
self.send(:extend, "::#{self.class.to_s.demodulize}".constantize) rescue Rails.logger.info "No extension defined for ::#{self.class.to_s}"
|
46
|
-
end
|
47
|
-
|
48
|
-
def id
|
49
|
-
@value
|
50
|
-
end
|
51
|
-
#returns escaped string from the object's to_s method
|
52
|
-
def to_s
|
53
|
-
ERB::Util::h(@value.to_s)
|
54
|
-
end
|
55
|
-
#returns html label tag for the representation
|
56
|
-
def label(value = nil, html_options = {})
|
57
|
-
tree = get_parents_tree
|
58
|
-
for_attr_value = tree.collect{ |x| x[0] }.join('_')
|
59
|
-
tags = get_tags(html_options, {:for => for_attr_value})
|
60
|
-
value = ERB::Util::h(@name.humanize) if value.nil?
|
61
|
-
%Q{<label #{tags}>#{value}</label>}
|
62
|
-
end
|
63
46
|
|
64
|
-
protected
|
65
|
-
#Call the passed block (if any)
|
66
|
-
def with_block(&block)
|
67
|
-
yield self if block_given?
|
68
|
-
end
|
69
|
-
#Returns two dimensional array based on the tree of the Represantation objects which are linked together by the @parent field
|
70
|
-
#First element of the array consists of Representation's @name and the second of Representation's class
|
71
|
-
def get_parents_tree
|
72
|
-
tree = Array.new
|
73
|
-
tree[0] = []
|
74
|
-
tree[0][0] = @name
|
75
|
-
tree[0][1] = self.class
|
76
|
-
parent = @parent
|
77
|
-
while parent do #iterate parent tree
|
78
|
-
array = []
|
79
|
-
array[0] = parent.instance_variable_get(:@name)
|
80
|
-
array[1] = parent.class
|
81
|
-
tree.unshift(array)
|
82
|
-
parent = parent.instance_variable_get(:@parent)
|
83
|
-
end
|
84
|
-
tree #tree now looks something like this [['user', ActiverRecordRepresentation], ['nick', DefaultRepresentation]]
|
85
|
-
end
|
86
|
-
#Creates value of the html name attribute according to the passed tree
|
87
|
-
def get_html_name_attribute_value(tree)
|
88
|
-
first = tree.delete_at(0)
|
89
|
-
root_name = first[0]
|
90
|
-
name = []
|
91
|
-
prev = nil
|
92
|
-
tree.each do |elem|
|
93
|
-
if elem[1] == DefaultRepresentation || elem[1] == TimeWithZoneRepresentation || prev == AssociationsRepresentation
|
94
|
-
name.push "[" + elem[0] + "]"
|
95
|
-
else
|
96
|
-
name.push "[" + elem[0] + "_attributes]"
|
97
|
-
end
|
98
|
-
prev = elem[1]
|
99
|
-
end
|
100
|
-
name.unshift(root_name)
|
101
|
-
end
|
102
|
-
#Returns string created by merging two hashes of html options passed as the arguments
|
103
|
-
def get_tags(user_options, base_options)
|
104
|
-
options = base_options.merge(user_options)
|
105
|
-
options.stringify_keys!
|
106
|
-
options = options.sort
|
107
|
-
options.map{ |key, value| %(#{key}="#{value}" ) }
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
class DefaultRepresentation < Representation
|
112
|
-
#not tested in the view
|
113
|
-
#Returns string with html check box tag
|
114
|
-
def check_box(checked_value = "1", unchecked_value = "0", html_options = {})
|
115
|
-
tree = get_parents_tree
|
116
|
-
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
117
|
-
name_attr_value = get_html_name_attribute_value(tree)
|
118
|
-
tags = get_tags(html_options, {:value => checked_value, :id => id_attr_value, :name=>name_attr_value})
|
119
|
-
%Q{<input type="checkbox" #{tags}/>\n<input type="hidden" value="#{unchecked_value}" id="#{id_attr_value}" name="#{name_attr_value}"/>}
|
120
|
-
end
|
121
|
-
#not tested in the view
|
122
|
-
#Returns string with html file field tag
|
123
|
-
def file_field(html_options = {})
|
124
|
-
tree = get_parents_tree
|
125
|
-
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
126
|
-
tags = get_tags(html_options, {:value => to_s, :id => id_attr_value, :name=>get_html_name_attribute_value(tree)})
|
127
|
-
%Q{<input type="file" #{tags}/>}
|
128
|
-
end
|
129
|
-
#not tested in the view
|
130
|
-
#Returns string with html hidden input tag
|
131
|
-
def hidden_field(html_options = {})
|
132
|
-
tree = get_parents_tree
|
133
|
-
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
134
|
-
tags = get_tags(html_options, {:value => to_s, :id => id_attr_value, :name=>get_html_name_attribute_value(tree)})
|
135
|
-
%Q{<input type="hidden" #{tags}/>}
|
136
|
-
end
|
137
|
-
#Returns string with html text input tag
|
138
|
-
def text_field(html_options = {})
|
139
|
-
tree = get_parents_tree
|
140
|
-
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
141
|
-
tags = get_tags(html_options, {:value => to_s, :id => id_attr_value, :name=>get_html_name_attribute_value(tree)})
|
142
|
-
%Q{<input type="text" #{tags}/>}
|
143
|
-
end
|
144
|
-
#Returns string with html text area tag
|
145
|
-
def text_area(html_options = {})
|
146
|
-
tree = get_parents_tree
|
147
|
-
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
148
|
-
tags = get_tags(html_options, {:id => id_attr_value, :name => get_html_name_attribute_value(tree)})
|
149
|
-
%Q{<textarea #{tags}>\n#{to_s}\n</textarea>}
|
150
|
-
end
|
151
|
-
#Returns string with html password tag
|
152
|
-
def password_field(html_options = {})
|
153
|
-
tree = get_parents_tree
|
154
|
-
id_attr_value = tree.collect{ |x| x[0] }.join('_')
|
155
|
-
tags = get_tags(html_options, {:value => to_s, :id => id_attr_value, :name=>get_html_name_attribute_value(tree)})
|
156
|
-
%Q{<input type="password" #{tags}/>}
|
157
|
-
end
|
158
|
-
#Returns string with html radio button tag
|
159
|
-
def radio_button(value, html_options = {})
|
160
|
-
tree = get_parents_tree
|
161
|
-
id_attr_value = tree.collect{ |x| x[0] }.join('_') + "_#{value}"
|
162
|
-
name_attr_value = get_html_name_attribute_value(tree)
|
163
|
-
tags = get_tags(html_options, {:name => name_attr_value, :value=>value, :id=>id_attr_value, :checked=>"#{@value.capitalize==value.capitalize}"})
|
164
|
-
%Q{<input type="radio" #{tags}/>}
|
165
|
-
end
|
166
|
-
#Returns string with html label tag with for attribute set to the radio button of this object
|
167
|
-
def radio_button_label(radio_button_value, value = nil, html_options = {})
|
168
|
-
tree = get_parents_tree
|
169
|
-
for_attr_value = tree.collect{ |x| x[0] }.join('_') + "_#{radio_button_value}"
|
170
|
-
value = radio_button_value.capitalize if value.nil?
|
171
|
-
tags = get_tags(html_options, {:for => for_attr_value})
|
172
|
-
%Q{<label #{tags}>#{ERB::Util::h(value)}</label>}
|
173
|
-
end
|
174
|
-
end
|
175
|
-
#Representation for objects which are nil
|
176
|
-
class NilClassRepresentation < Representation
|
177
|
-
#Returns self so the calls:
|
178
|
-
#nil_object.not_defined_method.another_not_defined_method
|
179
|
-
#want raise an error
|
180
|
-
def method_missing(method_name, *args)
|
181
|
-
return self
|
182
|
-
end
|
183
|
-
#Passed block shouldn't be called
|
184
|
-
def with_block(&block)
|
185
|
-
end
|
186
|
-
#Returns blank string
|
187
|
-
def to_s
|
188
|
-
return ''
|
189
|
-
end
|
190
|
-
end
|
191
|
-
#Representation for ActiveRecord::Base object's
|
192
|
-
class ActiveRecordRepresentation < Representation
|
193
|
-
#Form builder
|
194
|
-
def form(&block)
|
195
|
-
raise "You need to provide block to form representation" unless block_given?
|
196
|
-
content = @template.capture(self, &block)
|
197
|
-
@template.concat(@template.form_tag(@value))
|
198
|
-
@template.concat(content)
|
199
|
-
@template.concat("</form>")
|
200
|
-
self
|
201
|
-
end
|
202
|
-
#Forwards ActiveRecord invocation and wraps result in appropriate Representation
|
203
|
-
#Suppose that User extends ActiveRecord::Base :
|
204
|
-
#ar_user = User.new
|
205
|
-
#ar_user.nick = 'foo'
|
206
|
-
#user = r(ar_user) #user is now ActiveRecordRepresentation
|
207
|
-
#user.nick.text_field #method_missing will be called on user with method_name = 'nick' in which new method for user will be created and will be called. The newly created method will create a new DefaultRepresentation with @value set to the string 'foo'. Next the text_field will be called on the newly created DefaultRepresentation
|
208
|
-
def method_missing(method_name, *args, &block)
|
209
|
-
method = <<-EOF
|
210
|
-
def #{method_name}(*args, &block)
|
211
|
-
@__#{method_name} ||= Representations.representation_for(@value.#{method_name}, @template, "#{method_name}", self)
|
212
|
-
@__#{method_name}.with_block(&block)
|
213
|
-
@__#{method_name} if block.nil?
|
214
|
-
end
|
215
|
-
EOF
|
216
|
-
::Representations::ActiveRecordRepresentation.class_eval(method, __FILE__, __LINE__)
|
217
|
-
self.__send__(method_name, &block)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
#Representation for TimeWithZone object
|
221
|
-
class TimeWithZoneRepresentation < Representation
|
222
|
-
def select(passed_options = {}, html_options = {})
|
223
|
-
options = {:defaults => {:day => @value.day, :month => @value.month, :year => @value.year}}
|
224
|
-
options.merge!(passed_options)
|
225
|
-
tree = get_parents_tree
|
226
|
-
name = get_html_name_attribute_value(tree)
|
227
|
-
@template.date_select(name, @name, options, html_options)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
#Representation for Collections
|
231
|
-
class AssociationsRepresentation < Representation
|
232
|
-
#initilize @num variable
|
233
|
-
def initialize(object, template, name, parent)
|
234
|
-
super
|
235
|
-
@num = 0
|
236
|
-
end
|
237
|
-
#Creates Representation for every object in the Array and invokes passed block with this Representation as the argument
|
238
|
-
def each
|
239
|
-
@value.each do |object|
|
240
|
-
representation_object = Representations.representation_for(object, @template, object.id.to_s, self)
|
241
|
-
yield representation_object
|
242
|
-
end
|
243
|
-
end
|
244
|
-
#Creates new object in the collection and input fields for it defined in the passed block
|
245
|
-
def build
|
246
|
-
new_object = @value.build
|
247
|
-
representation_object = AssociationsRepresentation::NewRecordRepresentation.new(new_object, @template, 'new_' + num.to_s, self)
|
248
|
-
yield representation_object
|
249
|
-
end
|
250
|
-
private
|
251
|
-
attr_reader :num
|
252
|
-
#Used for generating unique @name for ArrayRepresentation::NewRecordRepresentation
|
253
|
-
def num
|
254
|
-
@num += 1
|
255
|
-
end
|
256
|
-
#Representation that wraps newly created ActiveRecord::Base that will be added to some collection
|
257
|
-
class NewRecordRepresentation < Representation
|
258
|
-
#Creates new method which wraps call for ActionRecord
|
259
|
-
#New method returns Representation which represents datatype in the appropriate column
|
260
|
-
def method_missing(method_name_symbol, *args, &block)
|
261
|
-
method_name = method_name_symbol.to_s
|
262
|
-
representation_class = case @value.class.columns_hash[method_name].type
|
263
|
-
when :string
|
264
|
-
"DefaultRepresentation"
|
265
|
-
when :date
|
266
|
-
"TimeWithZoneRepresentation"
|
267
|
-
end
|
268
|
-
method = <<-EOF
|
269
|
-
def #{method_name}(*args, &block)
|
270
|
-
@__#{method_name} ||= #{representation_class}.new(@value.#{method_name}, @template, "#{method_name}", self)
|
271
|
-
@__#{method_name}.with_block(&block)
|
272
|
-
@__#{method_name} if block.nil?
|
273
|
-
end
|
274
|
-
EOF
|
275
|
-
::Representations::AssociationsRepresentation::NewRecordRepresentation.class_eval(method, __FILE__, __LINE__)
|
276
|
-
self.__send__(method_name_symbol, &block)
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
280
47
|
end
|
data/lib/view_helpers.rb
CHANGED
@@ -1,15 +1,34 @@
|
|
1
1
|
module Representations
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
2
|
+
module ViewHelpers
|
3
|
+
def r(model)
|
4
|
+
if model.class == Representations::ActiveRecordRepresentation
|
5
|
+
Rails.logger.info 'Object is already wrapped in Representation'
|
6
|
+
r = model
|
7
|
+
else
|
8
|
+
if model.class == Array #model is an array of AR objects
|
9
|
+
model.map!{|m| representation_for(m, self, find_variable_name(model)) if m.is_a?(ActiveRecord::Base)}
|
10
|
+
else
|
11
|
+
r = Representations.representation_for(model, self, find_variable_name(model))
|
13
12
|
end
|
13
|
+
end
|
14
|
+
yield r if block_given?
|
15
|
+
r
|
16
|
+
end
|
17
|
+
#This method is aliased when Representations.enable_automatic_wrappint(true) is called
|
18
|
+
def instance_variable_set_with_r(symbol, obj)
|
19
|
+
load ActiveSupport::Dependencies.search_for_file('representations.rb')
|
20
|
+
if obj.is_a?(ActiveRecord::Base)
|
21
|
+
obj = Representations.representation_for(obj, self, symbol.to_s[1..-1])
|
22
|
+
elsif obj.class == Array #handle case when controller sends array of AR objects
|
23
|
+
obj.map!{|o| Representations.representation_for(o, self, symbol.to_s) if o.is_a?(ActiveRecord::Base)}
|
24
|
+
end
|
25
|
+
instance_variable_set_without_r(symbol, obj) #call to the original method
|
26
|
+
end
|
27
|
+
private
|
28
|
+
def find_variable_name(object)
|
29
|
+
self.instance_variables.each do |name|
|
30
|
+
return name[1..-1] if instance_variable_get(name) == object
|
31
|
+
end
|
14
32
|
end
|
33
|
+
end
|
15
34
|
end
|
data/representations.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{representations}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["\305\201ukasz Piestrzeniewicz", "Adam Sokolnicki"]
|
12
|
-
s.date = %q{2009-
|
12
|
+
s.date = %q{2009-11-12}
|
13
13
|
s.description = %q{Rails helpers, including form builders are great to allow rapid development of applications and views.
|
14
14
|
They are procedural in nature and have hard time to adapt to complex models. They also live in a single namespace making it difficult to find which helpers apply to which models.
|
15
15
|
Resource representations change syntax to object oriented and model specific.}
|
@@ -25,6 +25,12 @@ Resource representations change syntax to object oriented and model specific.}
|
|
25
25
|
"Rakefile",
|
26
26
|
"VERSION",
|
27
27
|
"init.rb",
|
28
|
+
"lib/active_record_representation.rb",
|
29
|
+
"lib/associations_representation.rb",
|
30
|
+
"lib/controller_helpers.rb",
|
31
|
+
"lib/date_representation.rb",
|
32
|
+
"lib/default_representation.rb",
|
33
|
+
"lib/representation.rb",
|
28
34
|
"lib/representations.rb",
|
29
35
|
"lib/view_helpers.rb",
|
30
36
|
"rails/init.rb",
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: representations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- "\xC5\x81ukasz Piestrzeniewicz"
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2009-
|
13
|
+
date: 2009-11-12 00:00:00 +01:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -33,6 +33,12 @@ files:
|
|
33
33
|
- Rakefile
|
34
34
|
- VERSION
|
35
35
|
- init.rb
|
36
|
+
- lib/active_record_representation.rb
|
37
|
+
- lib/associations_representation.rb
|
38
|
+
- lib/controller_helpers.rb
|
39
|
+
- lib/date_representation.rb
|
40
|
+
- lib/default_representation.rb
|
41
|
+
- lib/representation.rb
|
36
42
|
- lib/representations.rb
|
37
43
|
- lib/view_helpers.rb
|
38
44
|
- rails/init.rb
|