castle-her 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +17 -0
  5. data/.yardopts +2 -0
  6. data/CONTRIBUTING.md +26 -0
  7. data/Gemfile +10 -0
  8. data/LICENSE +7 -0
  9. data/README.md +1017 -0
  10. data/Rakefile +11 -0
  11. data/UPGRADE.md +110 -0
  12. data/castle-her.gemspec +30 -0
  13. data/gemfiles/Gemfile.activemodel-3.2.x +7 -0
  14. data/gemfiles/Gemfile.activemodel-4.0 +7 -0
  15. data/gemfiles/Gemfile.activemodel-4.1 +7 -0
  16. data/gemfiles/Gemfile.activemodel-4.2 +7 -0
  17. data/gemfiles/Gemfile.activemodel-5.0.x +7 -0
  18. data/lib/castle-her.rb +20 -0
  19. data/lib/castle-her/api.rb +113 -0
  20. data/lib/castle-her/collection.rb +12 -0
  21. data/lib/castle-her/errors.rb +27 -0
  22. data/lib/castle-her/json_api/model.rb +46 -0
  23. data/lib/castle-her/middleware.rb +12 -0
  24. data/lib/castle-her/middleware/accept_json.rb +17 -0
  25. data/lib/castle-her/middleware/first_level_parse_json.rb +36 -0
  26. data/lib/castle-her/middleware/json_api_parser.rb +36 -0
  27. data/lib/castle-her/middleware/parse_json.rb +21 -0
  28. data/lib/castle-her/middleware/second_level_parse_json.rb +36 -0
  29. data/lib/castle-her/model.rb +75 -0
  30. data/lib/castle-her/model/associations.rb +141 -0
  31. data/lib/castle-her/model/associations/association.rb +103 -0
  32. data/lib/castle-her/model/associations/association_proxy.rb +45 -0
  33. data/lib/castle-her/model/associations/belongs_to_association.rb +96 -0
  34. data/lib/castle-her/model/associations/has_many_association.rb +100 -0
  35. data/lib/castle-her/model/associations/has_one_association.rb +79 -0
  36. data/lib/castle-her/model/attributes.rb +284 -0
  37. data/lib/castle-her/model/base.rb +33 -0
  38. data/lib/castle-her/model/deprecated_methods.rb +61 -0
  39. data/lib/castle-her/model/http.rb +114 -0
  40. data/lib/castle-her/model/introspection.rb +65 -0
  41. data/lib/castle-her/model/nested_attributes.rb +45 -0
  42. data/lib/castle-her/model/orm.rb +207 -0
  43. data/lib/castle-her/model/parse.rb +216 -0
  44. data/lib/castle-her/model/paths.rb +126 -0
  45. data/lib/castle-her/model/relation.rb +164 -0
  46. data/lib/castle-her/version.rb +3 -0
  47. data/spec/api_spec.rb +114 -0
  48. data/spec/collection_spec.rb +26 -0
  49. data/spec/json_api/model_spec.rb +166 -0
  50. data/spec/middleware/accept_json_spec.rb +10 -0
  51. data/spec/middleware/first_level_parse_json_spec.rb +62 -0
  52. data/spec/middleware/json_api_parser_spec.rb +32 -0
  53. data/spec/middleware/second_level_parse_json_spec.rb +35 -0
  54. data/spec/model/associations/association_proxy_spec.rb +31 -0
  55. data/spec/model/associations_spec.rb +504 -0
  56. data/spec/model/attributes_spec.rb +389 -0
  57. data/spec/model/callbacks_spec.rb +145 -0
  58. data/spec/model/dirty_spec.rb +91 -0
  59. data/spec/model/http_spec.rb +158 -0
  60. data/spec/model/introspection_spec.rb +76 -0
  61. data/spec/model/nested_attributes_spec.rb +134 -0
  62. data/spec/model/orm_spec.rb +506 -0
  63. data/spec/model/parse_spec.rb +345 -0
  64. data/spec/model/paths_spec.rb +347 -0
  65. data/spec/model/relation_spec.rb +226 -0
  66. data/spec/model/validations_spec.rb +42 -0
  67. data/spec/model_spec.rb +44 -0
  68. data/spec/spec_helper.rb +26 -0
  69. data/spec/support/extensions/array.rb +5 -0
  70. data/spec/support/extensions/hash.rb +5 -0
  71. data/spec/support/macros/her_macros.rb +17 -0
  72. data/spec/support/macros/model_macros.rb +36 -0
  73. data/spec/support/macros/request_macros.rb +27 -0
  74. metadata +290 -0
@@ -0,0 +1,45 @@
1
+ module Her
2
+ module Model
3
+ module Associations
4
+ class AssociationProxy < (ActiveSupport.const_defined?('ProxyObject') ? ActiveSupport::ProxyObject : ActiveSupport::BasicObject)
5
+
6
+ # @private
7
+ def self.install_proxy_methods(target_name, *names)
8
+ names.each do |name|
9
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
10
+ def #{name}(*args, &block)
11
+ #{target_name}.send(#{name.inspect}, *args, &block)
12
+ end
13
+ RUBY
14
+ end
15
+ end
16
+
17
+ install_proxy_methods :association,
18
+ :build, :create, :where, :find, :all, :assign_nested_attributes
19
+
20
+ # @private
21
+ def initialize(association)
22
+ @_her_association = association
23
+ end
24
+
25
+ def association
26
+ @_her_association
27
+ end
28
+
29
+ # @private
30
+ def method_missing(name, *args, &block)
31
+ if :object_id == name # avoid redefining object_id
32
+ return association.fetch.object_id
33
+ end
34
+
35
+ # create a proxy to the fetched object's method
36
+ AssociationProxy.install_proxy_methods 'association.fetch', name
37
+
38
+ # resend message to fetched object
39
+ __send__(name, *args, &block)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,96 @@
1
+ module Her
2
+ module Model
3
+ module Associations
4
+ class BelongsToAssociation < Association
5
+
6
+ # @private
7
+ def self.attach(klass, name, opts)
8
+ opts = {
9
+ :class_name => name.to_s.classify,
10
+ :name => name,
11
+ :data_key => name,
12
+ :default => nil,
13
+ :foreign_key => "#{name}_id",
14
+ :path => "/#{name.to_s.pluralize}/:id"
15
+ }.merge(opts)
16
+ klass.associations[:belongs_to] << opts
17
+
18
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
19
+ def #{name}
20
+ cached_name = :"@_her_association_#{name}"
21
+
22
+ cached_data = (instance_variable_defined?(cached_name) && instance_variable_get(cached_name))
23
+ cached_data || instance_variable_set(cached_name, Her::Model::Associations::BelongsToAssociation.proxy(self, #{opts.inspect}))
24
+ end
25
+ RUBY
26
+ end
27
+
28
+ # @private
29
+ def self.parse(*args)
30
+ parse_single(*args)
31
+ end
32
+
33
+ # Initialize a new object
34
+ #
35
+ # @example
36
+ # class User
37
+ # include Her::Model
38
+ # belongs_to :organization
39
+ # end
40
+ #
41
+ # class Organization
42
+ # include Her::Model
43
+ # end
44
+ #
45
+ # user = User.find(1)
46
+ # new_organization = user.organization.build(:name => "Foo Inc.")
47
+ # new_organization # => #<Organization name="Foo Inc.">
48
+ def build(attributes = {})
49
+ @klass.build(attributes)
50
+ end
51
+
52
+ # Create a new object, save it and associate it to the parent
53
+ #
54
+ # @example
55
+ # class User
56
+ # include Her::Model
57
+ # belongs_to :organization
58
+ # end
59
+ #
60
+ # class Organization
61
+ # include Her::Model
62
+ # end
63
+ #
64
+ # user = User.find(1)
65
+ # user.organization.create(:name => "Foo Inc.")
66
+ # user.organization # => #<Organization id=2 name="Foo Inc.">
67
+ def create(attributes = {})
68
+ resource = build(attributes)
69
+ @parent.attributes[@name] = resource if resource.save
70
+ resource
71
+ end
72
+
73
+ # @private
74
+ def fetch
75
+ foreign_key_value = @parent.attributes[@opts[:foreign_key].to_sym]
76
+ data_key_value = @parent.attributes[@opts[:data_key].to_sym]
77
+ return @opts[:default].try(:dup) if (@parent.attributes.include?(@name) && @parent.attributes[@name].nil? && @params.empty?) || (foreign_key_value.blank? && data_key_value.blank?)
78
+
79
+ return @cached_result unless @params.any? || @cached_result.nil?
80
+ return @parent.attributes[@name] unless @params.any? || @parent.attributes[@name].blank?
81
+
82
+ path_params = @parent.attributes.merge(@params.merge(@klass.primary_key => foreign_key_value))
83
+ path = build_association_path lambda { @klass.build_request_path(path_params) }
84
+ @klass.get_resource(path, @params).tap do |result|
85
+ @cached_result = result if @params.blank?
86
+ end
87
+ end
88
+
89
+ # @private
90
+ def assign_nested_attributes(attributes)
91
+ assign_single_nested_attributes(attributes)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,100 @@
1
+ module Her
2
+ module Model
3
+ module Associations
4
+ class HasManyAssociation < Association
5
+
6
+ # @private
7
+ def self.attach(klass, name, opts)
8
+ opts = {
9
+ :class_name => name.to_s.classify,
10
+ :name => name,
11
+ :data_key => name,
12
+ :default => Her::Collection.new,
13
+ :path => "/#{name}",
14
+ :inverse_of => nil
15
+ }.merge(opts)
16
+ klass.associations[:has_many] << opts
17
+
18
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
19
+ def #{name}
20
+ cached_name = :"@_her_association_#{name}"
21
+
22
+ cached_data = (instance_variable_defined?(cached_name) && instance_variable_get(cached_name))
23
+ cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasManyAssociation.proxy(self, #{opts.inspect}))
24
+ end
25
+ RUBY
26
+ end
27
+
28
+ # @private
29
+ def self.parse(association, klass, data)
30
+ data_key = association[:data_key]
31
+ return {} unless data[data_key]
32
+
33
+ klass = klass.her_nearby_class(association[:class_name])
34
+ { association[:name] => Her::Model::Attributes.initialize_collection(klass, :data => data[data_key]) }
35
+ end
36
+
37
+ # Initialize a new object with a foreign key to the parent
38
+ #
39
+ # @example
40
+ # class User
41
+ # include Her::Model
42
+ # has_many :comments
43
+ # end
44
+ #
45
+ # class Comment
46
+ # include Her::Model
47
+ # end
48
+ #
49
+ # user = User.find(1)
50
+ # new_comment = user.comments.build(:body => "Hello!")
51
+ # new_comment # => #<Comment user_id=1 body="Hello!">
52
+ # TODO: This only merges the id of the parents, handle the case
53
+ # where this is more deeply nested
54
+ def build(attributes = {})
55
+ @klass.build(attributes.merge(:"#{@parent.singularized_resource_name}_id" => @parent.id))
56
+ end
57
+
58
+ # Create a new object, save it and add it to the associated collection
59
+ #
60
+ # @example
61
+ # class User
62
+ # include Her::Model
63
+ # has_many :comments
64
+ # end
65
+ #
66
+ # class Comment
67
+ # include Her::Model
68
+ # end
69
+ #
70
+ # user = User.find(1)
71
+ # user.comments.create(:body => "Hello!")
72
+ # user.comments # => [#<Comment id=2 user_id=1 body="Hello!">]
73
+ def create(attributes = {})
74
+ resource = build(attributes)
75
+
76
+ if resource.save
77
+ @parent.attributes[@name] ||= Her::Collection.new
78
+ @parent.attributes[@name] << resource
79
+ end
80
+
81
+ resource
82
+ end
83
+
84
+ # @private
85
+ def fetch
86
+ super.tap do |o|
87
+ inverse_of = @opts[:inverse_of] || @parent.singularized_resource_name
88
+ o.each { |entry| entry.send("#{inverse_of}=", @parent) }
89
+ end
90
+ end
91
+
92
+ # @private
93
+ def assign_nested_attributes(attributes)
94
+ data = attributes.is_a?(Hash) ? attributes.values : attributes
95
+ @parent.attributes[@name] = Her::Model::Attributes.initialize_collection(@klass, :data => data)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,79 @@
1
+ module Her
2
+ module Model
3
+ module Associations
4
+ class HasOneAssociation < Association
5
+
6
+ # @private
7
+ def self.attach(klass, name, opts)
8
+ opts = {
9
+ :class_name => name.to_s.classify,
10
+ :name => name,
11
+ :data_key => name,
12
+ :default => nil,
13
+ :path => "/#{name}"
14
+ }.merge(opts)
15
+ klass.associations[:has_one] << opts
16
+
17
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
18
+ def #{name}
19
+ cached_name = :"@_her_association_#{name}"
20
+
21
+ cached_data = (instance_variable_defined?(cached_name) && instance_variable_get(cached_name))
22
+ cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasOneAssociation.proxy(self, #{opts.inspect}))
23
+ end
24
+ RUBY
25
+ end
26
+
27
+ # @private
28
+ def self.parse(*args)
29
+ parse_single(*args)
30
+ end
31
+
32
+ # Initialize a new object with a foreign key to the parent
33
+ #
34
+ # @example
35
+ # class User
36
+ # include Her::Model
37
+ # has_one :role
38
+ # end
39
+ #
40
+ # class Role
41
+ # include Her::Model
42
+ # end
43
+ #
44
+ # user = User.find(1)
45
+ # new_role = user.role.build(:title => "moderator")
46
+ # new_role # => #<Role user_id=1 title="moderator">
47
+ def build(attributes = {})
48
+ @klass.build(attributes.merge(:"#{@parent.singularized_resource_name}_id" => @parent.id))
49
+ end
50
+
51
+ # Create a new object, save it and associate it to the parent
52
+ #
53
+ # @example
54
+ # class User
55
+ # include Her::Model
56
+ # has_one :role
57
+ # end
58
+ #
59
+ # class Role
60
+ # include Her::Model
61
+ # end
62
+ #
63
+ # user = User.find(1)
64
+ # user.role.create(:title => "moderator")
65
+ # user.role # => #<Role id=2 user_id=1 title="moderator">
66
+ def create(attributes = {})
67
+ resource = build(attributes)
68
+ @parent.attributes[@name] = resource if resource.save
69
+ resource
70
+ end
71
+
72
+ # @private
73
+ def assign_nested_attributes(attributes)
74
+ assign_single_nested_attributes(attributes)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,284 @@
1
+ module Her
2
+ module Model
3
+ # This module handles all methods related to model attributes
4
+ module Attributes
5
+ extend ActiveSupport::Concern
6
+
7
+ # Initialize a new object with data
8
+ #
9
+ # @param [Hash] attributes The attributes to initialize the object with
10
+ # @option attributes [Hash,Array] :_metadata
11
+ # @option attributes [Hash,Array] :_errors
12
+ # @option attributes [Boolean] :_destroyed
13
+ #
14
+ # @example
15
+ # class User
16
+ # include Her::Model
17
+ # end
18
+ #
19
+ # User.new(name: "Tobias") # => #<User name="Tobias">
20
+ def initialize(attributes={})
21
+ attributes ||= {}
22
+ @metadata = attributes.delete(:_metadata) || {}
23
+ @response_errors = attributes.delete(:_errors) || {}
24
+ @destroyed = attributes.delete(:_destroyed) || false
25
+
26
+ attributes = self.class.default_scope.apply_to(attributes)
27
+ assign_attributes(attributes)
28
+ run_callbacks :initialize
29
+ end
30
+
31
+ # Initialize a collection of resources
32
+ #
33
+ # @private
34
+ def self.initialize_collection(klass, parsed_data={})
35
+ collection_data = klass.extract_array(parsed_data).map do |item_data|
36
+ if item_data.kind_of?(klass)
37
+ resource = item_data
38
+ else
39
+ resource = klass.new(klass.parse(item_data))
40
+ resource.run_callbacks :find
41
+ end
42
+ resource
43
+ end
44
+ Her::Collection.new(collection_data, parsed_data[:metadata], parsed_data[:errors])
45
+ end
46
+
47
+ # Use setter methods of model for each key / value pair in params
48
+ # Return key / value pairs for which no setter method was defined on the model
49
+ #
50
+ # @private
51
+ def self.use_setter_methods(model, params)
52
+ params ||= {}
53
+
54
+ reserved_keys = [:id, model.class.primary_key] + model.class.association_keys
55
+ model.class.attributes *params.keys.reject { |k| reserved_keys.include?(k) || reserved_keys.map(&:to_s).include?(k) }
56
+
57
+ setter_method_names = model.class.setter_method_names
58
+ params.inject({}) do |memo, (key, value)|
59
+ setter_method = key.to_s + '='
60
+ if setter_method_names.include?(setter_method)
61
+ model.send(setter_method, value)
62
+ else
63
+ key = key.to_sym if key.is_a?(String)
64
+ memo[key] = value
65
+ end
66
+ memo
67
+ end
68
+ end
69
+
70
+ # Handles missing methods
71
+ #
72
+ # @private
73
+ def method_missing(method, *args, &blk)
74
+ if method.to_s =~ /[?=]$/ || @attributes.include?(method)
75
+ # Extract the attribute
76
+ attribute = method.to_s.sub(/[?=]$/, '')
77
+
78
+ # Create a new `attribute` methods set
79
+ self.class.attributes(*attribute)
80
+
81
+ # Resend the method!
82
+ send(method, *args, &blk)
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ # @private
89
+ def respond_to_missing?(method, include_private = false)
90
+ method.to_s.end_with?('=') || method.to_s.end_with?('?') || @attributes.include?(method) || super
91
+ end
92
+
93
+ # Assign new attributes to a resource
94
+ #
95
+ # @example
96
+ # class User
97
+ # include Her::Model
98
+ # end
99
+ #
100
+ # user = User.find(1) # => #<User id=1 name="Tobias">
101
+ # user.assign_attributes(name: "Lindsay")
102
+ # user.changes # => { :name => ["Tobias", "Lindsay"] }
103
+ def assign_attributes(new_attributes)
104
+ @attributes ||= attributes
105
+ # Use setter methods first
106
+ unset_attributes = Her::Model::Attributes.use_setter_methods(self, new_attributes)
107
+
108
+ # Then translate attributes of associations into association instances
109
+ parsed_attributes = self.class.parse_associations(unset_attributes)
110
+
111
+ # Then merge the parsed_data into @attributes.
112
+ @attributes.merge!(parsed_attributes)
113
+ end
114
+ alias attributes= assign_attributes
115
+
116
+ def attributes
117
+ @attributes ||= HashWithIndifferentAccess.new
118
+ end
119
+
120
+ # Handles returning true for the accessible attributes
121
+ #
122
+ # @private
123
+ def has_attribute?(attribute_name)
124
+ @attributes.include?(attribute_name)
125
+ end
126
+
127
+ # Handles returning data for a specific attribute
128
+ #
129
+ # @private
130
+ def get_attribute(attribute_name)
131
+ @attributes[attribute_name]
132
+ end
133
+ alias attribute get_attribute
134
+
135
+ # Return the value of the model `primary_key` attribute
136
+ def id
137
+ @attributes[self.class.primary_key]
138
+ end
139
+
140
+ # Return `true` if the other object is also a Her::Model and has matching data
141
+ #
142
+ # @private
143
+ def ==(other)
144
+ other.is_a?(Her::Model) && @attributes == other.attributes
145
+ end
146
+
147
+ # Delegate to the == method
148
+ #
149
+ # @private
150
+ def eql?(other)
151
+ self == other
152
+ end
153
+
154
+ # Delegate to @attributes, allowing models to act correctly in code like:
155
+ # [ Model.find(1), Model.find(1) ].uniq # => [ Model.find(1) ]
156
+ # @private
157
+ def hash
158
+ @attributes.hash
159
+ end
160
+
161
+ # Assign attribute value (ActiveModel convention method).
162
+ #
163
+ # @private
164
+ def attribute=(attribute, value)
165
+ @attributes[attribute] = nil unless @attributes.include?(attribute)
166
+ self.send(:"#{attribute}_will_change!") if @attributes[attribute] != value
167
+ @attributes[attribute] = value
168
+ end
169
+
170
+ # Check attribute value to be present (ActiveModel convention method).
171
+ #
172
+ # @private
173
+ def attribute?(attribute)
174
+ @attributes.include?(attribute) && @attributes[attribute].present?
175
+ end
176
+
177
+ module ClassMethods
178
+ # Initialize a collection of resources with raw data from an HTTP request
179
+ #
180
+ # @param [Array] parsed_data
181
+ # @private
182
+ def new_collection(parsed_data)
183
+ Her::Model::Attributes.initialize_collection(self, parsed_data)
184
+ end
185
+
186
+ # Initialize a new object with the "raw" parsed_data from the parsing middleware
187
+ #
188
+ # @private
189
+ def new_from_parsed_data(parsed_data)
190
+ parsed_data = parsed_data.with_indifferent_access
191
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
192
+ end
193
+
194
+ # Define attribute method matchers to automatically define them using ActiveModel's define_attribute_methods.
195
+ #
196
+ # @private
197
+ def define_attribute_method_matchers
198
+ attribute_method_suffix '='
199
+ attribute_method_suffix '?'
200
+ end
201
+
202
+ # Create a mutex for dynamically generated attribute methods or use one defined by ActiveModel.
203
+ #
204
+ # @private
205
+ def attribute_methods_mutex
206
+ @attribute_methods_mutex ||= if generated_attribute_methods.respond_to? :mu_synchronize
207
+ generated_attribute_methods
208
+ else
209
+ Mutex.new
210
+ end
211
+ end
212
+
213
+ # Define the attributes that will be used to track dirty attributes and validations
214
+ #
215
+ # @param [Array] attributes
216
+ # @example
217
+ # class User
218
+ # include Her::Model
219
+ # attributes :name, :email
220
+ # end
221
+ def attributes(*attributes)
222
+ attribute_methods_mutex.synchronize do
223
+ define_attribute_methods attributes
224
+ end
225
+ end
226
+
227
+ # Define the accessor in which the API response errors (obtained from the parsing middleware) will be stored
228
+ #
229
+ # @param [Symbol] store_response_errors
230
+ #
231
+ # @example
232
+ # class User
233
+ # include Her::Model
234
+ # store_response_errors :server_errors
235
+ # end
236
+ def store_response_errors(value = nil)
237
+ store_her_data(:response_errors, value)
238
+ end
239
+
240
+ # Define the accessor in which the API response metadata (obtained from the parsing middleware) will be stored
241
+ #
242
+ # @param [Symbol] store_metadata
243
+ #
244
+ # @example
245
+ # class User
246
+ # include Her::Model
247
+ # store_metadata :server_data
248
+ # end
249
+ def store_metadata(value = nil)
250
+ store_her_data(:metadata, value)
251
+ end
252
+
253
+ # @private
254
+ def setter_method_names
255
+ @_her_setter_method_names ||= instance_methods.inject(Set.new) do |memo, method_name|
256
+ memo << method_name.to_s if method_name.to_s.end_with?('=')
257
+ memo
258
+ end
259
+ end
260
+
261
+ private
262
+ # @private
263
+ def store_her_data(name, value)
264
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
265
+ if @_her_store_#{name} && value.present?
266
+ remove_method @_her_store_#{name}.to_sym
267
+ remove_method @_her_store_#{name}.to_s + '='
268
+ end
269
+
270
+ @_her_store_#{name} ||= begin
271
+ superclass.store_#{name} if superclass.respond_to?(:store_#{name})
272
+ end
273
+
274
+ return @_her_store_#{name} unless value
275
+ @_her_store_#{name} = value
276
+
277
+ define_method(value) { @#{name} }
278
+ define_method(value.to_s+'=') { |value| @#{name} = value }
279
+ RUBY
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end