glimmer-dsl-web 0.0.6 → 0.0.8

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.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -34,7 +34,6 @@ module Glimmer
34
34
  @selector = selector
35
35
  @listener = listener
36
36
  @js_listener = lambda do |js_event|
37
- # TODO wrap event with a Ruby Event object before passing to listener
38
37
  event = EventProxy.new(js_event: js_event, listener: self)
39
38
  result = listener.call(event)
40
39
  result = true if result.nil?
data/lib/glimmer/web.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,3 @@
1
- # TODO double check if the latest Opal implemented everything below already
2
1
  require 'date'
3
2
  require 'time'
4
3
 
@@ -13,6 +12,10 @@ class DateTime < Date
13
12
  end
14
13
  end
15
14
  end
15
+
16
+ def now
17
+ Time.now.to_datetime
18
+ end
16
19
 
17
20
  def to_date
18
21
  @time.to_date
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -0,0 +1,137 @@
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer-dsl-web'
23
+
24
+ class Address
25
+ attr_accessor :text
26
+ attr_reader :name, :street, :city, :state, :zip
27
+
28
+ def name=(value)
29
+ @name = value
30
+ update_text
31
+ end
32
+
33
+ def street=(value)
34
+ @street = value
35
+ update_text
36
+ end
37
+
38
+ def city=(value)
39
+ @city = value
40
+ update_text
41
+ end
42
+
43
+ def state=(value)
44
+ @state = value
45
+ update_text
46
+ end
47
+
48
+ def zip=(value)
49
+ @zip = value
50
+ update_text
51
+ end
52
+
53
+ private
54
+
55
+ def update_text
56
+ self.text = [name, street, city, state, zip].compact.reject(&:empty?).join(', ')
57
+ end
58
+ end
59
+
60
+ class User
61
+ attr_accessor :addresses
62
+ attr_reader :address_count
63
+
64
+ def initialize
65
+ @address_count = 1
66
+ @addresses = []
67
+ update_addresses
68
+ end
69
+
70
+ def address_count=(value)
71
+ value = [[1, value.to_i].max, 3].min
72
+ @address_count = value
73
+ update_addresses
74
+ end
75
+
76
+ private
77
+
78
+ def update_addresses
79
+ address_count_change = address_count - addresses.size
80
+ if address_count_change > 0
81
+ address_count_change.times { addresses << Address.new }
82
+ else
83
+ address_count_change.abs.times { addresses.pop }
84
+ end
85
+ end
86
+ end
87
+
88
+ @user = User.new
89
+
90
+ div {
91
+ div {
92
+ label('Number of addresses: ', for: 'address-count-field')
93
+ input(id: 'address-count-field', type: 'number', min: 1, max: 3) {
94
+ value <=> [@user, :address_count]
95
+ }
96
+ }
97
+
98
+ div {
99
+ # Content Data-Binding is used to dynamically (re)generate content of div
100
+ # based on changes to @user.addresses, replacing older content on every change
101
+ content(@user, :addresses) do
102
+ @user.addresses.each do |address|
103
+ div {
104
+ div(style: 'display: grid; grid-auto-columns: 80px 280px;') { |address_div|
105
+ [:name, :street, :city, :state, :zip].each do |attribute|
106
+ label(attribute.to_s.capitalize, for: "#{attribute}-field")
107
+ input(id: "#{attribute}-field", type: 'text') {
108
+ value <=> [address, attribute]
109
+ }
110
+ end
111
+
112
+ div(style: 'grid-column: 1 / span 2;') {
113
+ inner_text <= [address, :text]
114
+ }
115
+
116
+ style {
117
+ <<~CSS
118
+ #{address_div.selector} {
119
+ margin: 10px 0;
120
+ }
121
+ #{address_div.selector} * {
122
+ margin: 5px;
123
+ }
124
+ #{address_div.selector} label {
125
+ grid-column: 1;
126
+ }
127
+ #{address_div.selector} input, #{address_div.selector} select {
128
+ grid-column: 2;
129
+ }
130
+ CSS
131
+ }
132
+ }
133
+ }
134
+ end
135
+ end
136
+ }
137
+ }.render
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -21,7 +21,7 @@
21
21
 
22
22
  require 'glimmer-dsl-web'
23
23
 
24
- Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
24
+ Address = Struct.new(:street, :street2, :city, :state, :zip_code, :billing_and_shipping, keyword_init: true) do
25
25
  STATES = {
26
26
  "AK"=>"Alaska",
27
27
  "AL"=>"Alabama",
@@ -89,7 +89,10 @@ Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init:
89
89
  end
90
90
 
91
91
  def summary
92
- values.map(&:to_s).reject(&:empty?).join(', ')
92
+ string_attributes = to_h.except(:billing_and_shipping)
93
+ summary = string_attributes.values.map(&:to_s).reject(&:empty?).join(', ')
94
+ summary += " (Billing & Shipping)" if billing_and_shipping
95
+ summary
93
96
  end
94
97
  end
95
98
 
@@ -98,14 +101,15 @@ end
98
101
  street2: 'Apartment 3C, 2nd door to the right',
99
102
  city: 'San Diego',
100
103
  state: 'California',
101
- zip_code: '91911'
104
+ zip_code: '91911',
105
+ billing_and_shipping: true,
102
106
  )
103
107
 
104
108
  include Glimmer
105
109
 
106
110
  Document.ready? do
107
111
  div {
108
- form(style: 'display: grid; grid-auto-columns: 80px 200px;') { |address_form|
112
+ div(style: 'display: grid; grid-auto-columns: 80px 260px;') { |address_div|
109
113
  label('Street: ', for: 'street-field')
110
114
  input(id: 'street-field') {
111
115
  # Bidirectional Data-Binding with <=> ensures input.value and @address.street
@@ -139,16 +143,25 @@ Document.ready? do
139
143
  # on_write option specifies :to_s method to invoke on value before writing to model attribute
140
144
  # to ensure the numeric zip code value is stored as a String
141
145
  value <=> [@address, :zip_code,
142
- on_write: :to_s
146
+ on_write: :to_s,
143
147
  ]
144
148
  }
145
149
 
150
+ div(style: 'grid-column: 1 / span 2') {
151
+ input(id: 'billing-and-shipping-field', type: 'checkbox') {
152
+ checked <=> [@address, :billing_and_shipping]
153
+ }
154
+ label(for: 'billing-and-shipping-field') {
155
+ 'Use this address for both Billing & Shipping'
156
+ }
157
+ }
158
+
146
159
  style {
147
160
  <<~CSS
148
- .#{address_form.element_id} * {
161
+ #{address_div.selector} * {
149
162
  margin: 5px;
150
163
  }
151
- .#{address_form.element_id} input, .#{address_form.element_id} select {
164
+ #{address_div.selector} input, #{address_div.selector} select {
152
165
  grid-column: 2;
153
166
  }
154
167
  CSS
@@ -156,10 +169,12 @@ Document.ready? do
156
169
  }
157
170
 
158
171
  div(style: 'margin: 5px') {
159
- # Unidirectional Data-Binding is done with <= to ensure @address.summary changes update div.inner_text
160
- # as computed by changes to the address member attributes + state_code address custom attribute
172
+ # Unidirectional Data-Binding is done with <= to ensure @address.summary changes
173
+ # automatically update div.inner_text
174
+ # (computed by changes to address attributes, meaning if street changes,
175
+ # @address.summary is automatically recomputed.)
161
176
  inner_text <= [@address, :summary,
162
- computed_by: @address.members + ['state_code']
177
+ computed_by: @address.members + ['state_code'],
163
178
  ]
164
179
  }
165
180
  }.render
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -0,0 +1,117 @@
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer-dsl-web'
23
+
24
+ class TimePresenter
25
+ attr_accessor :date_time, :month_string, :week_string
26
+
27
+ def initialize
28
+ @date_time = Time.now
29
+ end
30
+
31
+ def month_string
32
+ @date_time&.strftime('%Y-%m')
33
+ end
34
+
35
+ def month_string=(value)
36
+ if value.match(/^\d{4}-\d{2}$/)
37
+ year, month = value.split('-')
38
+ self.date_time = Time.new(year, month, date_time.day, date_time.hour, date_time.min)
39
+ end
40
+ end
41
+
42
+ def week_string
43
+ return nil if @date_time.nil?
44
+ year = @date_time.year
45
+ week = ((@date_time.yday / 7).to_i + 1).to_s.rjust(2, '0')
46
+ "#{year}-W#{week}"
47
+ end
48
+
49
+ def date_time_string
50
+ @date_time&.strftime('%Y-%m-%dT%H:%M')
51
+ end
52
+
53
+ def date_time_string=(value)
54
+ if value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/)
55
+ date_time_parts = value.split('T')
56
+ date_parts = date_time_parts.first.split('-')
57
+ time_parts = date_time_parts.last.split(':')
58
+ self.date_time = Time.new(*date_parts, *time_parts)
59
+ end
60
+ end
61
+ end
62
+
63
+ @time_presenter = TimePresenter.new
64
+
65
+ include Glimmer
66
+
67
+ Document.ready? do
68
+ div {
69
+ div(style: 'display: grid; grid-auto-columns: 130px 260px;') { |container_div|
70
+ label('Date Time: ', for: 'date-time-field')
71
+ input(id: 'date-time-field', type: 'datetime-local') {
72
+ # Bidirectional Data-Binding with <=> ensures input.value and @time_presenter.date_time
73
+ # automatically stay in sync when either side changes
74
+ value <=> [@time_presenter, :date_time]
75
+ }
76
+
77
+ label('Date: ', for: 'date-field')
78
+ input(id: 'date-field', type: 'date') {
79
+ value <=> [@time_presenter, :date_time]
80
+ }
81
+
82
+ label('Time: ', for: 'time-field')
83
+ input(id: 'time-field', type: 'time') {
84
+ value <=> [@time_presenter, :date_time]
85
+ }
86
+
87
+ label('Month: ', for: 'month-field')
88
+ input(id: 'month-field', type: 'month') {
89
+ value <=> [@time_presenter, :month_string, computed_by: :date_time]
90
+ }
91
+
92
+ label('Week: ', for: 'week-field')
93
+ input(id: 'week-field', type: 'week', disabled: true) {
94
+ value <=> [@time_presenter, :week_string, computed_by: :date_time]
95
+ }
96
+
97
+ label('Time String: ', for: 'time-string-field')
98
+ input(id: 'time-string-field', type: 'text') {
99
+ value <=> [@time_presenter, :date_time_string, computed_by: :date_time]
100
+ }
101
+
102
+ style {
103
+ <<~CSS
104
+ #{container_div.selector} * {
105
+ margin: 5px;
106
+ }
107
+ #{container_div.selector} label {
108
+ grid-column: 1;
109
+ }
110
+ #{container_div.selector} input {
111
+ grid-column: 2;
112
+ }
113
+ CSS
114
+ }
115
+ }
116
+ }.render
117
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -26,7 +26,11 @@ GLIMMER_DSL_OPAL_LIB = File.join(GLIMMER_DSL_OPAL_ROOT, 'lib')
26
26
 
27
27
  $LOAD_PATH.unshift(GLIMMER_DSL_OPAL_LIB)
28
28
 
29
- if RUBY_ENGINE == 'opal'
29
+ if RUBY_ENGINE != 'opal'
30
+ require 'opal-rails'
31
+ require 'opal-async'
32
+ require 'opal-jquery'
33
+ else
30
34
  # GLIMMER_DSL_OPAL_MISSING = File.join(GLIMMER_DSL_OPAL_ROOT, 'lib', 'glimmer-dsl-opal', 'missing')
31
35
 
32
36
  # $LOAD_PATH.unshift(GLIMMER_DSL_OPAL_MISSING) # missing Ruby classes/methods
@@ -75,7 +79,7 @@ if RUBY_ENGINE == 'opal'
75
79
  require 'glimmer-dsl-xml'
76
80
  require 'glimmer-dsl-css'
77
81
 
78
- Glimmer::Config.loop_max_count = 150 # TODO consider disabling if preferred
82
+ Glimmer::Config.loop_max_count = 50 # TODO consider disabling if preferred
79
83
 
80
84
  original_logger_level = Glimmer::Config.logger.level
81
85
  Glimmer::Config.logger = Glimmer::Config::OpalLogger.new(STDOUT)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-01 00:00:00.000000000 Z
11
+ date: 2024-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.4.1
19
+ version: 2.7.6
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.4.1
26
+ version: 2.7.6
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: glimmer-dsl-xml
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.2.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: opal
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.4.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.4.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: opal-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 2.0.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 2.0.2
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: opal-async
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +94,20 @@ dependencies:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
96
  version: 1.4.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: opal-jquery
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.4.6
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.4.6
69
111
  - !ruby/object:Gem::Dependency
70
112
  name: to_collection
71
113
  requirement: !ruby/object:Gem::Requirement
@@ -188,34 +230,6 @@ dependencies:
188
230
  - - "~>"
189
231
  - !ruby/object:Gem::Version
190
232
  version: 0.8.0.alpha2
191
- - !ruby/object:Gem::Dependency
192
- name: opal-rails
193
- requirement: !ruby/object:Gem::Requirement
194
- requirements:
195
- - - "~>"
196
- - !ruby/object:Gem::Version
197
- version: 1.1.2
198
- type: :development
199
- prerelease: false
200
- version_requirements: !ruby/object:Gem::Requirement
201
- requirements:
202
- - - "~>"
203
- - !ruby/object:Gem::Version
204
- version: 1.1.2
205
- - !ruby/object:Gem::Dependency
206
- name: opal-jquery
207
- requirement: !ruby/object:Gem::Requirement
208
- requirements:
209
- - - "~>"
210
- - !ruby/object:Gem::Version
211
- version: 0.4.4
212
- type: :development
213
- prerelease: false
214
- version_requirements: !ruby/object:Gem::Requirement
215
- requirements:
216
- - - "~>"
217
- - !ruby/object:Gem::Version
218
- version: 0.4.4
219
233
  description: Glimmer DSL for Web (Ruby in the Browser Web GUI Frontend Library) -
220
234
  Enables frontend GUI development with Ruby by adopting a DSL that follows web-like
221
235
  HTML syntax, enabling the transfer of HTML/CSS/JS skills to Ruby frontend development.
@@ -239,13 +253,16 @@ files:
239
253
  - lib/glimmer-dsl-web/ext/date.rb
240
254
  - lib/glimmer-dsl-web/ext/exception.rb
241
255
  - lib/glimmer-dsl-web/samples/hello/hello_button.rb
256
+ - lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb
242
257
  - lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb
243
258
  - lib/glimmer-dsl-web/samples/hello/hello_form.rb
259
+ - lib/glimmer-dsl-web/samples/hello/hello_input_date_time.rb
244
260
  - lib/glimmer-dsl-web/samples/hello/hello_world.rb
245
261
  - lib/glimmer-dsl-web/vendor/jquery.js
246
262
  - lib/glimmer/config/opal_logger.rb
247
263
  - lib/glimmer/data_binding/element_binding.rb
248
264
  - lib/glimmer/dsl/web/bind_expression.rb
265
+ - lib/glimmer/dsl/web/content_data_binding_expression.rb
249
266
  - lib/glimmer/dsl/web/data_binding_expression.rb
250
267
  - lib/glimmer/dsl/web/dsl.rb
251
268
  - lib/glimmer/dsl/web/element_expression.rb
@@ -260,7 +277,6 @@ files:
260
277
  - lib/glimmer/web/element_proxy.rb
261
278
  - lib/glimmer/web/event_proxy.rb
262
279
  - lib/glimmer/web/listener_proxy.rb
263
- - lib/glimmer/web/property_owner.rb
264
280
  homepage: http://github.com/AndyObtiva/glimmer-dsl-web
265
281
  licenses:
266
282
  - MIT
@@ -1,24 +0,0 @@
1
- module Glimmer
2
- module Web
3
- # Adapts Glimmer UI classes to SWT JavaBean property owner classes (which are now adapted to Opal)
4
- module PropertyOwner
5
- # TODO consider adding has_attribute?
6
-
7
- def get_attribute(attribute_name)
8
- send(attribute_getter(attribute_name))
9
- end
10
-
11
- def set_attribute(attribute_name, *args)
12
- send(attribute_setter(attribute_name), *args) unless args.size == 1 && send(attribute_getter(attribute_name)) == args.first
13
- end
14
-
15
- def attribute_setter(attribute_name)
16
- "#{attribute_name.to_s.underscore}="
17
- end
18
-
19
- def attribute_getter(attribute_name)
20
- attribute_name.to_s.underscore
21
- end
22
- end
23
- end
24
- end