attribution 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 47f71dbe5313d245cc8548a49367c9ce1f33a4c0
4
- data.tar.gz: 88bffa818cdb84d3a2c3575972af941a5e47e339
3
+ metadata.gz: d198859156d76d0e0c11d018834a556df0dbdfcb
4
+ data.tar.gz: 0a103445301e0ea36e62bf12b6d2089b015b9496
5
5
  SHA512:
6
- metadata.gz: 7d6660fca38237e02cd6b042b8c67ad8c4de68e29a70997eb96f0522068562d47455dec03d4855b50f7a4d7b8a9245279cb9276489f7a9827d96799f75a6a034
7
- data.tar.gz: 7c1da374f1d0728561d20d6e02179f44c3e7554029e2562616cafc5690f89cc4bd88b3898bfd0f94da36a505399605d011d1bf5fa9608ffc70a7bdd4940ec420
6
+ metadata.gz: 053aeb28529a3da23866dfdf01f4ccc53d1c9d07517c9e2e22647acef67d73614800b69517f7984b39af2112235f5cda34caab72a3390b8958e7b65bad754629
7
+ data.tar.gz: 380264f5c4aaf9df41bc2030de3c281d914983d7482939bcd54b54a2195bf8158b79a3d76a693c97f6ce58ee0aa7c9a5ec7ff44bcecf88b38affac8a9dd6f0fe
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.0.0-p0
1
+ 2.0.0-p247
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
4
  Rake::TestTask.new do |t|
5
- t.libs = ["lib"]
5
+ t.libs = ["lib", "test"]
6
6
  t.test_files = FileList["test/*_test.rb"]
7
7
  end
8
8
 
data/attribution.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.name = "attribution"
5
- gem.version = "0.6.3"
5
+ gem.version = "0.6.4"
6
6
  gem.authors = ["Paul Barry"]
7
7
  gem.email = ["mail@paulbarry.com"]
8
8
  gem.description = %q{Add attributes to Ruby objects}
@@ -1,3 +1,3 @@
1
1
  module Attribution
2
- VERSION = "0.6.3"
2
+ VERSION = "0.6.4"
3
3
  end
data/lib/attribution.rb CHANGED
@@ -16,6 +16,7 @@ module Attribution
16
16
  self.attributes = attributes
17
17
  end
18
18
 
19
+ # @return [Hash] the attributes of this instance and their values
19
20
  def attributes(*associations)
20
21
  self.class.attribute_names.inject({}) do |attrs, attr|
21
22
  attrs[attr] = send(attr)
@@ -24,6 +25,7 @@ module Attribution
24
25
  end
25
26
  alias_method :to_h, :attributes
26
27
 
28
+ # @param [String, Hash] attributes The attributes and their values
27
29
  def attributes=(attributes)
28
30
  attributes = case attributes
29
31
  when String then JSON.parse(attributes)
@@ -40,6 +42,10 @@ module Attribution
40
42
  end
41
43
 
42
44
  module ClassMethods
45
+
46
+ # @param [Hash, Attribution] obj The Hash or Object to convert to
47
+ # an instance of this class
48
+ # @return [Attribution] An instance of this class
43
49
  def cast(obj)
44
50
  case obj
45
51
  when Hash then new(obj)
@@ -48,6 +54,8 @@ module Attribution
48
54
  end
49
55
  end
50
56
 
57
+ # @return [Hash{Symbol => Object}] Each attribute name, type and
58
+ # any related metadata in the order in which they were defined
51
59
  def attributes
52
60
  @attributes ||= if superclass && superclass.respond_to?(:attributes)
53
61
  superclass.attributes.dup
@@ -56,16 +64,28 @@ module Attribution
56
64
  end
57
65
  end
58
66
 
67
+ # @return [Array<Symbol>] The names of the attributes
68
+ # in the order in which they were defined
59
69
  def attribute_names
60
70
  @attribute_names ||= attributes.map{|a| a[:name] }
61
71
  end
62
72
 
73
+ # Attribute macros
74
+
75
+ # Defines an attribute
76
+ #
77
+ # @param [String] name The name of the attribute
78
+ # @param [Symbol] type The type of the attribute
79
+ # @param [Hash{Symbol => Object}] metadata The metadata for the attribute
63
80
  def add_attribute(name, type, metadata={})
64
81
  attr_reader name
65
82
  attributes << (metadata || {}).merge(:name => name.to_sym, :type => type.to_sym)
66
83
  end
67
84
 
68
- # Attribute macros
85
+ # Defines a string attribute
86
+ #
87
+ # @param [Symbol] attr The name of the attribute
88
+ # @param [Hash{Symbol => Object}] metadata The metadata for the attribute
69
89
  def string(attr, metadata={})
70
90
  add_attribute(attr, :string, metadata)
71
91
  define_method("#{attr}=") do |arg|
@@ -73,6 +93,10 @@ module Attribution
73
93
  end
74
94
  end
75
95
 
96
+ # Defines a boolean attribute
97
+ #
98
+ # @param [Symbol] attr The name of the attribute
99
+ # @param [Hash{Symbol => Object}] metadata The metadata for the attribute
76
100
  def boolean(attr, metadata={})
77
101
  add_attribute(attr, :boolean, metadata)
78
102
  define_method("#{attr}=") do |arg|
@@ -87,6 +111,10 @@ module Attribution
87
111
  alias_method "#{attr}?", attr
88
112
  end
89
113
 
114
+ # Defines a integer attribute
115
+ #
116
+ # @param [Symbol] attr The name of the attribute
117
+ # @param [Hash{Symbol => Object}] metadata The metadata for the attribute
90
118
  def integer(attr, metadata={})
91
119
  add_attribute(attr, :integer, metadata)
92
120
  define_method("#{attr}=") do |arg|
@@ -94,6 +122,10 @@ module Attribution
94
122
  end
95
123
  end
96
124
 
125
+ # Defines a float attribute
126
+ #
127
+ # @param [Symbol] attr The name of the attribute
128
+ # @param [Hash{Symbol => Object}] metadata The metadata for the attribute
97
129
  def float(attr, metadata={})
98
130
  add_attribute(attr, :float, metadata)
99
131
  define_method("#{attr}=") do |arg|
@@ -101,6 +133,10 @@ module Attribution
101
133
  end
102
134
  end
103
135
 
136
+ # Defines a decimal attribute
137
+ #
138
+ # @param [Symbol] attr The name of the attribute
139
+ # @param [Hash{Symbol => Object}] metadata The metadata for the attribute
104
140
  def decimal(attr, metadata={})
105
141
  add_attribute(attr, :decimal, metadata)
106
142
  define_method("#{attr}=") do |arg|
@@ -108,6 +144,10 @@ module Attribution
108
144
  end
109
145
  end
110
146
 
147
+ # Defines a date attribute
148
+ #
149
+ # @param [Symbol] attr The name of the attribute
150
+ # @param [Hash{Symbol => Object}] metadata The metadata for the attribute
111
151
  def date(attr, metadata={})
112
152
  add_attribute(attr, :date, metadata)
113
153
  define_method("#{attr}=") do |arg|
@@ -125,6 +165,10 @@ module Attribution
125
165
  end
126
166
  end
127
167
 
168
+ # Defines a time attribute
169
+ #
170
+ # @param [Symbol] attr The name of the attribute
171
+ # @param [Hash{Symbol => Object}] metadata The metadata for the attribute
128
172
  def time(attr, metadata={})
129
173
  add_attribute(attr, :time, metadata)
130
174
  define_method("#{attr}=") do |arg|
@@ -142,6 +186,10 @@ module Attribution
142
186
  end
143
187
  end
144
188
 
189
+ # Defines a time zone attribute, based on ActiveSupport::TimeZone
190
+ #
191
+ # @param [Symbol] attr The name of the attribute
192
+ # @param [Hash{Symbol => Object}] metadata The metadata for the attribute
145
193
  def time_zone(attr, metadata={})
146
194
  add_attribute(attr, :time_zone, metadata)
147
195
  define_method("#{attr}=") do |arg|
@@ -150,10 +198,20 @@ module Attribution
150
198
  end
151
199
 
152
200
  # Association macros
201
+
202
+ # Defines an attribute that is a reference to another Attribution class.
203
+ #
204
+ # @param [Symbol] association_name The name of the association
205
+ # @param [Hash] metadata Extra information about the association.
206
+ # @option metadata [String] :class_name Class of the association,
207
+ # defaults to a class name based on the association name
153
208
  def belongs_to(association_name, metadata={})
154
209
  # foo_id
155
210
  id_getter = "#{association_name}_id".to_sym
156
211
  add_attribute(id_getter, :integer, metadata)
212
+
213
+ association_class_name = metadata.try(:fetch, :class_name, [name.deconstantize, association_name.to_s.classify].reject(&:blank?).join('::'))
214
+
157
215
  define_method(id_getter) do
158
216
  ivar = "@#{id_getter}"
159
217
  if instance_variable_defined?(ivar)
@@ -177,11 +235,12 @@ module Attribution
177
235
  if instance_variable_defined?("@#{association_name}")
178
236
  instance_variable_get("@#{association_name}")
179
237
  elsif id = instance_variable_get("@#{association_name}_id")
238
+
180
239
  # TODO: Support a more generic version of lazy-loading
181
240
  begin
182
- association_class = association_name.to_s.classify.constantize
241
+ association_class = Object.const_get(association_class_name)
183
242
  rescue NameError => ex
184
- raise ArgumentError.new("Association #{association_name} in #{self.class} is invalid because #{association_name.to_s.classify} does not exist")
243
+ raise ArgumentError.new("Association #{association_name} in #{self.class} is invalid because #{association_class_name} does not exist")
185
244
  end
186
245
 
187
246
  if association_class.respond_to?(:find)
@@ -195,9 +254,9 @@ module Attribution
195
254
  # foo=
196
255
  define_method("#{association_name}=") do |arg|
197
256
  begin
198
- association_class = association_name.to_s.classify.constantize
257
+ association_class = Object.const_get(association_class_name)
199
258
  rescue NameError => ex
200
- raise ArgumentError.new("Association #{association_name} in #{self.class} is invalid because #{association_name.to_s.classify} does not exist")
259
+ raise ArgumentError.new("Association #{association_name} in #{self.class} is invalid because #{association_class_name} does not exist")
201
260
  end
202
261
 
203
262
  if instance_variable_defined?("@#{association_name}_id")
@@ -207,15 +266,24 @@ module Attribution
207
266
  end
208
267
  end
209
268
 
210
- def has_many(association_name)
269
+ # Defines an attribute that is a reference to an Array of another Attribution class.
270
+ #
271
+ # @param [Symbol] association_name The name of the association
272
+ # @param [Hash] metadata Extra information about the association.
273
+ # @option metadata [String] :class_name Class of the association,
274
+ # defaults to a class name based on the association name
275
+ def has_many(association_name, metadata={})
276
+
277
+ association_class_name = metadata.try(:fetch, :class_name, [name.deconstantize, association_name.to_s.singularize.classify].reject(&:blank?).join('::'))
278
+
211
279
  # foos
212
280
  define_method(association_name) do |*query|
213
281
 
214
282
  # TODO: Support a more generic version of lazy-loading
215
283
  begin
216
- association_class = association_name.to_s.classify.constantize
284
+ association_class = Object.const_get(association_class_name)
217
285
  rescue NameError => ex
218
- raise ArgumentError.new("Association #{association_name} in #{self.class} is invalid because #{association_name.to_s.classify} does not exist")
286
+ raise ArgumentError.new("Association #{association_name} in #{self.class} is invalid because #{association_class_name} does not exist")
219
287
  end
220
288
 
221
289
  if query.empty? # Ex: Books.all, so we want to cache it.
@@ -227,7 +295,7 @@ module Attribution
227
295
  end
228
296
  else # Ex: Book.all(:name => "The..."), so we do not want to cache it
229
297
  if association_class.respond_to?(:all)
230
- Array(association_class.all({"#{self.class.name.underscore}_id" => id}.merge(query.first)))
298
+ Array(association_class.all({"#{self.class.name.demodulize.underscore}_id" => id}.merge(query.first)))
231
299
  end
232
300
  end
233
301
  end
@@ -236,15 +304,23 @@ module Attribution
236
304
  define_method("#{association_name}=") do |arg|
237
305
  # TODO: put this in method
238
306
  begin
239
- association_class = association_name.to_s.classify.constantize
307
+ association_class = Object.const_get(association_class_name)
240
308
  rescue NameError => ex
241
- raise ArgumentError.new("Association #{association_name} in #{self.class} is invalid because #{association_name.to_s.classify} does not exist")
309
+ raise ArgumentError.new("Association #{association_name} in #{self.class} is invalid because #{association_class_name} does not exist")
242
310
  end
243
311
 
312
+ attr_name = self.class.name.demodulize.underscore
244
313
  objs = (arg.is_a?(Hash) ? arg.values : Array(arg)).map do |obj|
245
314
  o = association_class.cast(obj)
246
- o.send("#{self.class.name.underscore}=", self)
247
- o.send("#{self.class.name.underscore}_id=", id)
315
+
316
+ if o.respond_to?("#{attr_name}=")
317
+ o.send("#{attr_name}=", self)
318
+ end
319
+
320
+ if o.respond_to?("#{attr_name}_id=") && respond_to?(:id)
321
+ o.send("#{attr_name}_id=", id)
322
+ end
323
+
248
324
  o
249
325
  end
250
326
  instance_variable_set("@#{association_name}", objs)
@@ -4,7 +4,7 @@ require 'attribution/model'
4
4
  class Article
5
5
  include Attribution::Model
6
6
 
7
- string :title, required: true, format: { with: /^\w/, message: "must start with a letter" }, length: 4..20
7
+ string :title, required: true, format: { with: /\A\w/, message: "must start with a letter" }, length: 4..20
8
8
  end
9
9
 
10
10
  class AttributionModelTest < Test::Unit::TestCase
@@ -11,10 +11,10 @@ class Address
11
11
  string :state
12
12
  string :zip
13
13
 
14
- belongs_to :author
14
+ belongs_to :author, :class_name => 'Person'
15
15
  end
16
16
 
17
- class Author
17
+ class Person
18
18
  include Attribution
19
19
 
20
20
  integer :id
@@ -94,6 +94,32 @@ class Grandchild < Child
94
94
  string :baz
95
95
  end
96
96
 
97
+ module Music
98
+
99
+ end
100
+
101
+ module Music
102
+ class Album
103
+ include Attribution
104
+
105
+ has_many :tracks
106
+
107
+ string :artist
108
+ string :title
109
+ end
110
+ end
111
+
112
+ module Music
113
+ class Track
114
+ include Attribution
115
+
116
+ belongs_to :album
117
+
118
+ integer :number
119
+ string :title
120
+ end
121
+ end
122
+
97
123
  class AttributionTest < Test::Unit::TestCase
98
124
 
99
125
  def test_create
@@ -254,11 +280,21 @@ class AttributionTest < Test::Unit::TestCase
254
280
  end
255
281
 
256
282
  def test_has_many_association_name
257
- author = Author.new(addresses: [{id: 1}])
258
- assert_equal [1], author.addresses.map(&:id)
283
+ person = Person.new(addresses: [{id: 1}])
284
+ assert_equal [1], person.addresses.map(&:id)
259
285
  end
260
286
 
261
287
  def test_attribute_inheritence
262
288
  assert_equal [:foo, :bar, :baz], Grandchild.attribute_names
263
289
  end
290
+
291
+ def test_namespaced_belongs_to
292
+ track = Music::Track.new(number: 1, title: "Elevator Music", album: { artist: "Beck", title: "The Information" })
293
+ assert_equal "Beck", track.album.artist
294
+ end
295
+
296
+ def test_namespaced_has_many
297
+ album = Music::Album.new(artist: "Beck", title: "The Information", tracks: [ { number: 1, title: "Elevator Music" } ])
298
+ assert_equal "Elevator Music", album.tracks.first.title
299
+ end
264
300
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attribution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Barry
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-09 00:00:00.000000000 Z
11
+ date: 2013-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -95,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
95
  version: '0'
96
96
  requirements: []
97
97
  rubyforge_project:
98
- rubygems_version: 2.0.0
98
+ rubygems_version: 2.0.3
99
99
  signing_key:
100
100
  specification_version: 4
101
101
  summary: Add attributes to Ruby objects
@@ -103,3 +103,4 @@ test_files:
103
103
  - test/attribution_model_test.rb
104
104
  - test/attribution_test.rb
105
105
  - test/test_helper.rb
106
+ has_rdoc: