attribution 0.6.3 → 0.6.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.
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: