attribute_mapper 1.3.0 → 1.3.1
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/lib/attribute_mapper.rb +44 -15
- data/test/attribute_mapper_test.rb +70 -38
- metadata +16 -4
data/lib/attribute_mapper.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module AttributeMapper
|
2
|
-
|
2
|
+
|
3
3
|
def self.included(model)
|
4
4
|
model.extend ClassMethods
|
5
5
|
model.send(:include, InstanceMethods)
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
module ClassMethods # @private
|
9
|
-
|
9
|
+
|
10
10
|
# Map a column in your table to a human-friendly attribute on your
|
11
11
|
# model. When +attribute+ is accessed, it will return the key
|
12
12
|
# from the mapping hash. When the attribute is updated, the value
|
@@ -25,6 +25,7 @@ module AttributeMapper
|
|
25
25
|
# Each attribute you map generates an options method, suitable for
|
26
26
|
# use in form helpers. If you define an attribute +status+,
|
27
27
|
# instances of your model will have a +status_options+ method
|
28
|
+
# (on the class and any instances)
|
28
29
|
# that returns a sorted array of arrays containing
|
29
30
|
# humanized-option-name/value pairs. By default this array is
|
30
31
|
# sorted by the option name (closed/open/etc.) If you'd rather
|
@@ -42,16 +43,32 @@ module AttributeMapper
|
|
42
43
|
# @option options :predicate_methods Generate methods for checking
|
43
44
|
# whether an object has a certain attribute set
|
44
45
|
def map_attribute(attribute, options)
|
45
|
-
mapping = options
|
46
|
+
mapping = build_mapping(options)
|
46
47
|
verify_existence_of attribute
|
47
48
|
add_accessor_for attribute, mapping
|
48
49
|
add_predicates_for attribute, mapping.keys if options.fetch(:predicate_methods) { true }
|
49
50
|
override attribute
|
50
51
|
add_options_helper_for attribute, mapping
|
52
|
+
add_options_helper_to_class attribute, self
|
51
53
|
end
|
52
54
|
|
53
55
|
private
|
54
|
-
|
56
|
+
def build_mapping(options)
|
57
|
+
case options[:to]
|
58
|
+
when Hash
|
59
|
+
options[:to]
|
60
|
+
when Array
|
61
|
+
build_mapping_from_array(options[:to])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Transforms an array into a hash for the mapping.
|
66
|
+
# [:open, :closed] # => { :open => 1, :closed => 2 }
|
67
|
+
#
|
68
|
+
def build_mapping_from_array(array)
|
69
|
+
array.enum_for(:each_with_index).inject({}) { |h, (o, i)| h[o] = i+1; h }
|
70
|
+
end
|
71
|
+
|
55
72
|
def add_accessor_for(attribute, mapping)
|
56
73
|
class_eval(<<-EVAL, __FILE__, __LINE__)
|
57
74
|
class << self
|
@@ -78,18 +95,30 @@ module AttributeMapper
|
|
78
95
|
options = self.class.#{attribute.to_s.pluralize}.sort { |l, r|
|
79
96
|
sort_by_keys ? l.first.to_s <=> r.first.to_s : l.last <=> r.last
|
80
97
|
}.map { |f|
|
81
|
-
[f[0].to_s.
|
98
|
+
[f[0].to_s.capitalize, f[0]]
|
82
99
|
}
|
83
|
-
|
100
|
+
self.#{attribute}.present? ? [options, {:selected => self.#{attribute}}] : [options]
|
84
101
|
end
|
85
102
|
EVAL
|
86
103
|
end
|
87
|
-
|
104
|
+
|
105
|
+
def add_options_helper_to_class(attribute, klass)
|
106
|
+
klass.instance_eval(<<-EVAL, __FILE__, __LINE__)
|
107
|
+
def #{attribute}_options(sort_by_keys=true)
|
108
|
+
options = #{attribute.to_s.pluralize}.sort { |l, r|
|
109
|
+
sort_by_keys ? l.first.to_s <=> r.first.to_s : l.last <=> r.last
|
110
|
+
}.map { |f|
|
111
|
+
[f[0].to_s.capitalize, f[0]]
|
112
|
+
}
|
113
|
+
end
|
114
|
+
EVAL
|
115
|
+
end
|
116
|
+
|
88
117
|
def override(*args)
|
89
118
|
override_getters *args
|
90
119
|
override_setters *args
|
91
120
|
end
|
92
|
-
|
121
|
+
|
93
122
|
def override_getters(attribute)
|
94
123
|
class_eval(<<-EVAL, __FILE__, __LINE__)
|
95
124
|
def #{attribute}
|
@@ -97,7 +126,7 @@ module AttributeMapper
|
|
97
126
|
end
|
98
127
|
EVAL
|
99
128
|
end
|
100
|
-
|
129
|
+
|
101
130
|
def override_setters(attribute)
|
102
131
|
class_eval(<<-EVAL, __FILE__, __LINE__)
|
103
132
|
def #{attribute}=(raw_value)
|
@@ -106,15 +135,15 @@ module AttributeMapper
|
|
106
135
|
end
|
107
136
|
EVAL
|
108
137
|
end
|
109
|
-
|
138
|
+
|
110
139
|
def verify_existence_of(attribute)
|
111
140
|
raise ArgumentError, "`#{attribute}' is not an attribute of `#{self}'" unless column_names.include?(attribute.to_s)
|
112
141
|
end
|
113
|
-
|
142
|
+
|
114
143
|
end
|
115
|
-
|
144
|
+
|
116
145
|
module InstanceMethods
|
117
|
-
|
146
|
+
|
118
147
|
private
|
119
148
|
|
120
149
|
def resolve_value_of(attribute, raw_value)
|
@@ -126,5 +155,5 @@ module AttributeMapper
|
|
126
155
|
end
|
127
156
|
|
128
157
|
end
|
129
|
-
|
158
|
+
|
130
159
|
end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class AttributeMapperTest < Test::Unit::TestCase
|
4
|
-
|
4
|
+
|
5
5
|
context "Attribute Mapper" do
|
6
6
|
setup do
|
7
7
|
Ticket.map_attribute :status, :to => mapping
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
should "set mapping for each attribute" do
|
11
11
|
assert_equal mapping[:open], Ticket.statuses[:open]
|
12
12
|
assert_equal mapping[:closed], Ticket.statuses[:closed]
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
should "override getters and setters" do
|
16
16
|
assert_nil ticket.status
|
17
17
|
assert_nothing_raised do
|
@@ -19,11 +19,11 @@ class AttributeMapperTest < Test::Unit::TestCase
|
|
19
19
|
end
|
20
20
|
assert_equal :open, ticket.status
|
21
21
|
assert_equal mapping[:open], ticket[:status]
|
22
|
-
|
22
|
+
|
23
23
|
assert_nothing_raised do
|
24
24
|
ticket.status = :closed
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
assert_equal :closed, ticket.status
|
28
28
|
assert_equal mapping[:closed], ticket[:status]
|
29
29
|
end
|
@@ -32,7 +32,7 @@ class AttributeMapperTest < Test::Unit::TestCase
|
|
32
32
|
ticket.status = :open
|
33
33
|
assert ticket.open?
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
should "allow indifferent access to setters" do
|
37
37
|
assert_nothing_raised do
|
38
38
|
ticket.status = :open
|
@@ -43,27 +43,27 @@ class AttributeMapperTest < Test::Unit::TestCase
|
|
43
43
|
end
|
44
44
|
assert_equal :open, ticket.status
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
should "raise an exception when trying to map to a non-existent column" do
|
48
48
|
assert_raises(ArgumentError) do
|
49
49
|
Ticket.map_attribute :this_column_does_not_exist, :to => {:i_will_fail => 1}
|
50
50
|
end
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
should "raise an exception when setting an invalid value" do
|
54
54
|
assert_raises(ArgumentError) do
|
55
55
|
ticket.status = :non_existent_value
|
56
56
|
end
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
should "allow setting a primitive value directly if the value is present in the mapping" do
|
60
60
|
ticket = Ticket.new
|
61
61
|
assert_nothing_raised do
|
62
62
|
ticket.status = mapping[:open]
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
assert_equal :open, ticket.status
|
66
|
-
|
66
|
+
|
67
67
|
assert_raises(ArgumentError) do
|
68
68
|
ticket.status = 500
|
69
69
|
end
|
@@ -76,10 +76,10 @@ class AttributeMapperTest < Test::Unit::TestCase
|
|
76
76
|
should "work with attr_accessible" do
|
77
77
|
new_ticket = Class.new(ActiveRecord::Base) do
|
78
78
|
set_table_name "tickets"
|
79
|
-
|
79
|
+
|
80
80
|
include AttributeMapper
|
81
81
|
map_attribute :status, :to => {:open => 1, :closed => 2}
|
82
|
-
|
82
|
+
|
83
83
|
attr_accessible :status
|
84
84
|
end
|
85
85
|
|
@@ -95,6 +95,40 @@ class AttributeMapperTest < Test::Unit::TestCase
|
|
95
95
|
ticket.status = :open
|
96
96
|
assert_equal [[["Open", :open], ["Closed", :closed]], {:selected => :open}], ticket.status_options(false)
|
97
97
|
end
|
98
|
+
|
99
|
+
should "provide a class level helper for forms" do
|
100
|
+
assert_equal [["Closed", :closed], ["Open", :open]], Ticket.status_options
|
101
|
+
assert_equal [["Open", :open], ["Closed", :closed]], Ticket.status_options(false)
|
102
|
+
end
|
103
|
+
|
104
|
+
should "not raise an error when setting to a blank value" do
|
105
|
+
assert_nothing_raised {
|
106
|
+
ticket.update_attributes(:status => "")
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
should "not raise an error when setting to a nil value" do
|
111
|
+
assert_nothing_raised {
|
112
|
+
ticket.update_attributes(:status => nil)
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
should "turn off generation of predicate methods" do
|
117
|
+
new_ticket = Class.new(ActiveRecord::Base) do
|
118
|
+
set_table_name "tickets"
|
119
|
+
|
120
|
+
include AttributeMapper
|
121
|
+
map_attribute :status, :to => {:open => 1, :closed => 2}, :predicate_methods => false
|
122
|
+
end
|
123
|
+
|
124
|
+
t = new_ticket.new
|
125
|
+
t.status = :open
|
126
|
+
|
127
|
+
assert_raises(NoMethodError) {
|
128
|
+
t.open?
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
98
132
|
|
99
133
|
context "setting nil as a valid value in the mapping" do
|
100
134
|
setup do
|
@@ -120,46 +154,44 @@ class AttributeMapperTest < Test::Unit::TestCase
|
|
120
154
|
|
121
155
|
end
|
122
156
|
|
123
|
-
should
|
124
|
-
|
125
|
-
ticket.update_attributes(:status => "")
|
126
|
-
}
|
127
|
-
end
|
128
|
-
|
129
|
-
should "not raise an error when setting to a nil value" do
|
130
|
-
assert_nothing_raised {
|
131
|
-
ticket.update_attributes(:status => nil)
|
132
|
-
}
|
133
|
-
end
|
134
|
-
|
135
|
-
should "turn off generation of predicate methods" do
|
136
|
-
new_ticket = Class.new(ActiveRecord::Base) do
|
157
|
+
should 'auto-expand arrays to a hash with the raw value being the index + 1' do
|
158
|
+
array_ticket = Class.new(ActiveRecord::Base) do
|
137
159
|
set_table_name "tickets"
|
138
160
|
|
139
161
|
include AttributeMapper
|
140
|
-
map_attribute :status, :to =>
|
162
|
+
map_attribute :status, :to => [:open, :closed]
|
141
163
|
end
|
142
164
|
|
143
|
-
|
144
|
-
|
165
|
+
ticket = array_ticket.new
|
166
|
+
|
167
|
+
assert_nil ticket.status
|
168
|
+
assert_nothing_raised do
|
169
|
+
ticket.status = :open
|
170
|
+
end
|
171
|
+
assert_equal :open, ticket.status
|
172
|
+
assert_equal mapping[:open], ticket[:status]
|
173
|
+
|
174
|
+
assert_nothing_raised do
|
175
|
+
ticket.status = :closed
|
176
|
+
end
|
177
|
+
|
178
|
+
assert_equal :closed, ticket.status
|
179
|
+
assert_equal mapping[:closed], ticket[:status]
|
145
180
|
|
146
|
-
assert_raises(NoMethodError) {
|
147
|
-
t.open?
|
148
|
-
}
|
149
181
|
end
|
150
|
-
|
182
|
+
|
151
183
|
end
|
152
|
-
|
184
|
+
|
153
185
|
#######
|
154
186
|
private
|
155
187
|
#######
|
156
|
-
|
188
|
+
|
157
189
|
def mapping(options = {})
|
158
190
|
{:open => 1, :closed => 2}.merge(options)
|
159
191
|
end
|
160
|
-
|
192
|
+
|
161
193
|
def ticket
|
162
194
|
@ticket ||= Ticket.new
|
163
195
|
end
|
164
|
-
|
196
|
+
|
165
197
|
end
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attribute_mapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 3
|
9
|
+
- 1
|
10
|
+
version: 1.3.1
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Marcel Molina Jr.
|
@@ -38,21 +44,27 @@ rdoc_options:
|
|
38
44
|
require_paths:
|
39
45
|
- lib
|
40
46
|
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
41
48
|
requirements:
|
42
49
|
- - ">="
|
43
50
|
- !ruby/object:Gem::Version
|
51
|
+
hash: 3
|
52
|
+
segments:
|
53
|
+
- 0
|
44
54
|
version: "0"
|
45
|
-
version:
|
46
55
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
47
57
|
requirements:
|
48
58
|
- - ">="
|
49
59
|
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
50
63
|
version: "0"
|
51
|
-
version:
|
52
64
|
requirements: []
|
53
65
|
|
54
66
|
rubyforge_project:
|
55
|
-
rubygems_version: 1.3.
|
67
|
+
rubygems_version: 1.3.7
|
56
68
|
signing_key:
|
57
69
|
specification_version: 3
|
58
70
|
summary: Map symbolic types to primitive types and stash them in a column.
|