structure 0.12.2 → 0.13.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/.gitignore CHANGED
@@ -1,4 +1,4 @@
1
- pkg/*
2
1
  *.gem
3
2
  .bundle
3
+ pkg/*
4
4
  Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - jruby
5
+ - rbx
data/CHANGELOG.md CHANGED
@@ -1,21 +1,26 @@
1
- # 0.8
1
+ # 0.13
2
2
 
3
- * Make JSON patch compatible with Active Support.
4
- * Remove URI from list of types.
3
+ * Remove static module.
4
+ * Rename .key to .attribute.
5
5
 
6
- # 0.9
6
+ # 0.12
7
7
 
8
- * Add presence method.
8
+ * Add static module.
9
+
10
+ # 0.11
11
+
12
+ * .key now emulates DataMapper.property.
9
13
 
10
14
  # 0.10
11
15
 
12
- * Rename has_one and has_many to embeds_one and embeds_many to make room
16
+ * Rename .has_one and .has_many to .embeds_one and .embeds_many to make room
13
17
  for associations.
14
18
 
15
- # 0.11
19
+ # 0.9
16
20
 
17
- * .key now emulates the DataMapper.property method.
21
+ * Add presence method.
18
22
 
19
- # 0.12
23
+ # 0.8
20
24
 
21
- * Add basic support for static models.
25
+ * Make JSON patch compatible with Active Support.
26
+ * Remove URI from list of types.
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
- source "http://rubygems.org"
1
+ source :rubygems
2
2
 
3
- # Specify your gem's dependencies in structure.gemspec
4
- gemspec
3
+ gem 'activesupport', '>= 3.0'
4
+ gem 'json', :platform => [:mri_18, :jruby]
5
+ gem 'rake', '~> 0.9'
data/README.md CHANGED
@@ -1,101 +1,69 @@
1
- Structure
2
- =========
3
-
4
- Structure is a key/value container for modeling ephemeral data in Ruby.
5
-
6
- Structure typecasts, nests other structures, and talks flawless JSON.
7
-
8
- #_ d
9
- ##_ d#
10
- NN#p j0NN
11
- 40NNh_ _gN#B0
12
- 4JF@NNp_ _g0WNNL@
13
- JLE5@WRNp_ _g@NNNF3_L
14
- _F`@q4WBN@Np_ _gNN@ZL#p"Fj_
15
- "0^#-LJ_9"NNNMp__ _gN#@#"R_#g@q^9"
16
- a0,3_j_j_9FN@N@0NMp__ __ggNZNrNM"P_f_f_E,0a
17
- j L 6 9""Q"#^q@NDNNNMpg____ ____gggNNW#W4p^p@jF"P"]"j F
18
- rNrr4r*pr4r@grNr@q@Ng@q@N0@N#@NNMpmggggmqgNN@NN@#@4p*@M@p4qp@w@m@Mq@r#rq@r
19
- F Jp 9__b__M,Juw*w*^#^9#""EED*dP_@EZ@^E@*#EjP"5M"gM@p*Ww&,jL_J__f F j
20
- -r#^^0""E" 6 q q__hg-@4""*,_Z*q_"^pwr""p*C__@""0N-qdL_p" p J" 3""5^^0r-
21
- t J __,Jb--N""", *_s0M`""q_a@NW__JP^u_p"""p4a,p" _F""V--wL,_F_ F #
22
- _,Jp*^#""9 L 5_a*N"""q__INr" "q_e^"*,p^""qME_ y"""p6u,f j' f "N^--LL_
23
- L ] k,w@#"""_ "_a*^E ba-" ^qj-""^pe" J^-u_f _f "q@w,j f jL
24
- #_,J@^""p `_ _jp-""q _Dw^" ^cj*""*,j^ "p#_ y""^wE_ _F F"^qN,_j
25
- w*^0 4 9__sAF" `L _Dr" m__m""q__a^"m__* "qA_ j" ""Au__f J 0^--
26
- ] J_,x-E 3_ jN^" `u _w^*_ _RR_ _J^w_ j" "pL_ f 7^-L_F #
27
- jLs*^6 `_ _&*" q _,NF "wp" "*g" _NL_ p "-d_ F ]"*u_F
28
- ,x-"F ] Ax^" q hp" `u jM""u a^ ^, j" "*g_ p ^mg_ D.H. 1992
29
-
30
-
31
- Usage
32
- -----
33
-
34
- Define a model:
1
+ # Structure
35
2
 
36
- ```ruby
37
- require 'structure'
3
+ [![travis](https://secure.travis-ci.org/papercavalier/structure.png)](http://travis-ci.org/papercavalier/structure)
38
4
 
39
- class Person < Structure
40
- key :name, :default => "John Doe"
41
- key :age, Integer
42
- embeds_many :friends
43
- embeds_one :partner
44
- end
45
- ```
5
+ Structure is Ruby module that turns a class into a key/value container.
6
+
7
+ ## Usage
46
8
 
47
- Create an object:
9
+ Set up models.
48
10
 
49
11
  ```ruby
50
- p1 = Person.new :name => 'Gilles'
51
- ```
12
+ require 'structure'
52
13
 
53
- Typecast:
14
+ class Book
15
+ include Structure
54
16
 
55
- ```ruby
56
- p1.age = '28'
57
- p1.age
58
- => 28
17
+ attribute :title
18
+ attribute :binding, :default => "Hardcover"
19
+ attribute :year_published, Integer
20
+ embeds_many :authors
21
+ end
22
+
23
+ class Author
24
+ include Structure
25
+
26
+ attribute :name
27
+ attribute :role
28
+ end
59
29
  ```
60
30
 
61
- Check for presence:
31
+ Create some objects.
62
32
 
63
33
  ```ruby
64
- p1.age?
65
- => true
34
+ book = Book.new :title => "A Thousand Plateaus"
35
+ author = Author.new :name => "Gilles Deleuze"
36
+ book.authors << author
66
37
  ```
67
38
 
68
-
69
- Embed other structures:
39
+ Attributes in structures are typecasted.
70
40
 
71
41
  ```ruby
72
- p2 = Person.new
73
- p1.friends << p2
42
+ book.year_published = "1985"
43
+ puts book.year_published
44
+ => 1985
74
45
  ```
75
46
 
76
- Talk JSON:
47
+ Translate to JSON and back into Ruby.
77
48
 
78
49
  ```ruby
79
- require 'structure/json'
50
+ json = book.to_json
51
+ puts json
52
+ => {"json_class":"Book","title":"A Thousand Plateaus","binding":"Hardcover,"year_published":1985,"authors":[{"json_class":"Author","name":"Gilles Deleuze","role":null}]}
80
53
 
81
- json = p1.to_json
82
- => {"json_class":"Person","name":"Gilles","age":28,"friends":[{"json_class":"Person","name":"John Doe","age":null,"friends":[]}],"partner":null}
83
-
84
- person = JSON.parse(json)
85
- person.friends.first
86
- => #<Person:0x0000010107d030 @attributes={:name=>"John Doe", :age=>nil, :friends=>[], :partner=>nil}>
54
+ book = JSON.parse(json)
55
+ puts book.authors.first.name
56
+ => "Gilles Deleuze"
87
57
  ```
88
58
 
89
- Quack Active Model:
59
+ Mix in Active Model modules.
90
60
 
91
61
  ```ruby
92
62
  require 'active_model'
93
63
 
94
- class Book < Structure
64
+ class Book
95
65
  include ActiveModel::Validations
96
66
 
97
- key :title
98
-
99
67
  validates_presence_of :title
100
68
  end
101
69
 
@@ -104,20 +72,4 @@ book.valid?
104
72
  => false
105
73
  book.errors
106
74
  => {:title=>["can't be blank"]}
107
- book.title = "Society of the Spectacle"
108
- book.valid?
109
- => true
110
75
  ```
111
-
112
- Types
113
- -----
114
-
115
- Structure supports the following types:
116
-
117
- * Array
118
- * Boolean
119
- * Float
120
- * Hash
121
- * Integer
122
- * String
123
- * Structure
data/Rakefile CHANGED
@@ -1,10 +1,9 @@
1
- require 'bundler'
2
- require 'rspec/core/rake_task'
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
3
 
4
- Bundler::GemHelper.install_tasks
4
+ task :default => :test
5
5
 
6
- RSpec::Core::RakeTask.new(:spec) do |t|
7
- t.pattern = "spec/**/*_spec.rb"
6
+ Rake::TestTask.new do |test|
7
+ test.libs << 'test'
8
+ test.test_files = FileList['test/**/*_test.rb']
8
9
  end
9
-
10
- task :default => [:spec]
data/lib/structure.rb CHANGED
@@ -4,94 +4,95 @@ rescue NameError
4
4
  require 'json'
5
5
  end
6
6
 
7
- # Ruby doesn't have a Boolean class, so let's feign one.
8
- unless Object.const_defined?(:Boolean)
7
+ # Fabricate a +Boolean+ class.
8
+ unless defined? Boolean
9
9
  module Boolean; end
10
- class TrueClass; include Boolean; end
11
- class FalseClass; include Boolean; end
10
+ [TrueClass, FalseClass].each { |klass| klass.send :include, Boolean }
12
11
  end
13
12
 
14
- # A key/value container for modeling ephemeral data.
15
- class Structure
13
+ # Structure is a Ruby module that turns a class into a key/value container.
14
+ #
15
+ # class Person
16
+ # include Structure
17
+ #
18
+ # attribute :name
19
+ # attribute :age, Integer
20
+ # end
21
+ #
22
+ # person = Person.new(:name => "John")
23
+ # person.name
24
+ # => "John"
25
+ #
26
+ module Structure
16
27
  include Enumerable
17
28
 
29
+ # Structure supports the following types.
18
30
  TYPES = [Array, Boolean, Float, Hash, Integer, String, Structure]
19
31
 
20
- class << self
21
- # A shortcut to define an attribute that represents an array of other
22
- # objects, possibly structures.
23
- def embeds_many(name)
24
- key name, Array, :default => []
32
+ module ClassMethods
33
+ # Defines an attribute that represents an array of objects, possibly
34
+ # structures.
35
+ def embeds_many(key)
36
+ attribute key, Array, :default => []
25
37
  end
26
38
 
27
- # A shortcut to define an attribute that represents another structure.
28
- def embeds_one(name)
29
- key name, Structure
39
+ # Defines an attribute that represents another structure.
40
+ def embeds_one(key)
41
+ attribute key, Structure
30
42
  end
31
43
 
44
+ # Builds a structure out of its JSON representation.
32
45
  def json_create(object)
33
- object.delete('json_class')
34
- new(object)
46
+ object.delete 'json_class'
47
+ new object
35
48
  end
36
49
 
37
- # Defines an attribute key.
50
+ # Defines an attribute.
38
51
  #
39
- # Takes a name, an optional type, and an optional hash of options.
52
+ # Takes a key, an optional type, and an optional hash of options.
40
53
  #
41
- # The type can be Array, Boolean, Float, Hash, Integer, String, or
42
- # Structure. If none is specified, it defaults to String.
54
+ # The type can be +Array+, +Boolean+, +Float+, +Hash+, +Integer+, +String+,
55
+ # or +Structure+. If none is specified, this defaults to String.
43
56
  #
44
57
  # Available options are:
45
58
  #
46
- # * :default, which sets the default value for the attribute.
47
- #
48
- # class Book
49
- # key :title
50
- # key :authors, Array, :default => []
51
- # end
52
- def key(name, *args)
53
- name = name.to_sym
54
- options = if args.last.is_a? Hash
55
- args.pop
56
- else
57
- {}
58
- end
59
- type = args.shift || String
60
-
61
- if method_defined?(name)
62
- raise NameError, "#{name} is already defined"
63
- end
59
+ # * +:default+, which sets the default value for the attribute.
60
+ def attribute(key, *args)
61
+ key = key.to_sym
62
+ options = args.last.is_a?(Hash) ? args.pop : {}
63
+ type = args.shift || String
64
+ default = options[:default]
64
65
 
65
- unless TYPES.include? type
66
- raise TypeError, "#{type} is not a valid type"
66
+ if method_defined? key
67
+ raise NameError, "#{key} is already defined"
67
68
  end
68
69
 
69
- default = options[:default]
70
- unless default.nil? || default.is_a?(type)
71
- raise TypeError, "#{default} is not #{%w{AEIOU}.include?(type.to_s[0]) ? 'an' : 'a'} #{type}"
70
+ if TYPES.include?(type) && (default.nil? || default.is_a?(type))
71
+ default_attributes[key] = default
72
+ else
73
+ msg = "#{default} is not a#{'n' if type.to_s.match(/^[AI]/)} #{type}"
74
+ raise TypeError, msg
72
75
  end
73
76
 
74
- default_attributes[name] = default
75
-
76
77
  module_eval do
77
-
78
- # Define a proc to typecast value.
78
+ # Define a closure that typecasts value.
79
79
  typecast =
80
80
  if type == Boolean
81
81
  lambda do |value|
82
82
  case value
83
+ when Boolean
84
+ value
83
85
  when String
84
- value =~ /1|true/i
86
+ value !~ /0|false/i
85
87
  when Integer
86
88
  value != 0
87
89
  else
88
- !!value
90
+ !!value
89
91
  end
90
92
  end
91
93
  elsif [Hash, Structure].include? type
92
-
93
- # Raise an exception rather than typecast if type is Hash or
94
- # Structure.
94
+ # Don't bother with typecasting attributes of type +Hash+ or
95
+ # +Structure+.
95
96
  lambda do |value|
96
97
  unless value.is_a? type
97
98
  raise TypeError, "#{value} is not a #{type}"
@@ -102,16 +103,17 @@ class Structure
102
103
  lambda { |value| Kernel.send(type.to_s, value) }
103
104
  end
104
105
 
105
- # Define a getter.
106
- define_method(name) { @attributes[name] }
106
+ # Define attribute accessors.
107
+ define_method(key) { @attributes[key] }
107
108
 
108
- # Define a setter.
109
- define_method("#{name}=") do |value|
110
- @attributes[name] = value.nil? ? nil : typecast.call(value)
109
+ define_method("#{key}=") do |value|
110
+ @attributes[key] = value.nil? ? nil : typecast.call(value)
111
111
  end
112
112
 
113
- # Define a "presence" (for lack of a better term) method
114
- define_method("#{name}?") { !!@attributes[name] }
113
+ # Define a method to check for presence.
114
+ unless type == Array
115
+ define_method("#{key}?") { !!@attributes[key] }
116
+ end
115
117
  end
116
118
  end
117
119
 
@@ -121,23 +123,27 @@ class Structure
121
123
  end
122
124
  end
123
125
 
126
+ def self.included(base)
127
+ base.extend(ClassMethods)
128
+ end
129
+
124
130
  # Creates a new structure.
125
131
  #
126
- # Optionally, seeds the structure with a hash of attributes.
127
- def initialize(seed = {})
132
+ # A hash, if provided, will seed its attributes.
133
+ def initialize(hash = {})
128
134
  @attributes = {}
129
135
  self.class.default_attributes.each do |key, value|
130
136
  @attributes[key] = value.is_a?(Array) ? value.dup : value
131
137
  end
132
138
 
133
- seed.each { |key, value| self.send("#{key}=", value) }
139
+ hash.each { |key, value| self.send("#{key}=", value) }
134
140
  end
135
141
 
136
- # A hash of attributes.
142
+ # A hash that stores the attributes of the structure.
137
143
  attr_reader :attributes
138
144
 
145
+ # Returns a Rails-friendly JSON representation of the structure.
139
146
  def as_json(options = nil)
140
- # create a subset of the attributes by applying :only or :except
141
147
  subset = if options
142
148
  if attrs = options[:only]
143
149
  @attributes.slice(*Array.wrap(attrs))
@@ -155,6 +161,8 @@ class Structure
155
161
  merge(subset)
156
162
  end
157
163
 
164
+ # Calls block once for each attribute in the structure, passing that
165
+ # attribute as a parameter.
158
166
  def each(&block)
159
167
  @attributes.each { |value| block.call(value) }
160
168
  end
@@ -164,6 +172,7 @@ class Structure
164
172
  @attributes.keys
165
173
  end
166
174
 
175
+ # Returns a JSON representation of the structure.
167
176
  def to_json(*args)
168
177
  klass = self.class.name
169
178
  { JSON.create_id => klass }.
@@ -1,3 +1,3 @@
1
- class Structure
2
- VERSION = '0.12.2'
1
+ module Structure
2
+ VERSION = '0.13.1'
3
3
  end
data/structure.gemspec CHANGED
@@ -8,23 +8,12 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Paper Cavalier"]
10
10
  s.email = ["code@papercavalier.com"]
11
- s.homepage = "http://rubygems.com/gems/structure"
12
- s.summary = "A key/value container for modeling ephemeral data"
13
- s.description = <<-END_OF_DESCRIPTION.strip
14
- Structure is a key/value container for modeling ephemeral data.
15
- END_OF_DESCRIPTION
11
+ s.homepage = "http://code.papercavalier.com/structure"
12
+ s.summary = "Turns a class into a key/value container"
13
+ s.description = "Structure turns a class into a key/value container."
16
14
 
17
15
  s.rubyforge_project = "structure"
18
16
 
19
- {
20
- 'activesupport' => '>= 3.0',
21
- 'rake' => '~> 0.9',
22
- 'rspec' => '~> 2.6',
23
- 'ruby-debug19' => '~> 0.11.6'
24
- }.each do |lib, version|
25
- s.add_development_dependency lib, version
26
- end
27
-
28
17
  s.files = `git ls-files`.split("\n")
29
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
30
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -1,4 +1,8 @@
1
- class Book < Structure
1
+ class Book
2
+ include Structure
3
+
2
4
  key :title
3
5
  key :authors, Array, :value => []
4
6
  end
7
+
8
+
@@ -0,0 +1,134 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'test/unit'
4
+
5
+ require File.expand_path("../../lib/structure", __FILE__)
6
+
7
+ class Person
8
+ include Structure
9
+
10
+ attribute :name
11
+ attribute :age, Integer
12
+ attribute :married, Boolean, :default => false
13
+ embeds_one :spouse
14
+ embeds_many :children
15
+ end
16
+
17
+ class TestStructure < Test::Unit::TestCase
18
+ def test_enumeration
19
+ person = Person.new
20
+ assert_respond_to(person, :map)
21
+ end
22
+
23
+ def test_method_generation
24
+ person = Person.new
25
+ assert_respond_to(person, :name)
26
+ assert_respond_to(person, :name=)
27
+ assert_respond_to(person, :name?)
28
+ end
29
+
30
+ def test_attribute_errors
31
+ assert_raise(NameError) { Person.attribute :class }
32
+ assert_raise(TypeError) { Person.attribute :foo, Object }
33
+ assert_raise(TypeError) { Person.attribute :foo, :default => 1 }
34
+ end
35
+
36
+ def test_default_attributes
37
+ assert_equal({ :name => nil, :age => nil, :married => false, :spouse => nil, :children => [] }, Person.default_attributes)
38
+ end
39
+
40
+ def test_initialization
41
+ person = Person.new(:name => 'John', :age => 28)
42
+ assert_equal('John', person.name)
43
+ assert_equal(28, person.age)
44
+ end
45
+
46
+ def test_typecasting
47
+ person = Person.new
48
+
49
+ person.age = "28"
50
+ assert_equal(28, person.age)
51
+
52
+ person.age = nil
53
+ assert_nil(person.age)
54
+ end
55
+
56
+ def test_presence
57
+ person = Person.new
58
+
59
+ person.married = nil
60
+ assert(!person.married?)
61
+
62
+ person.married = false
63
+ assert(!person.married?)
64
+
65
+ person.married = true
66
+ assert(person.married?)
67
+ end
68
+
69
+ def test_default_type
70
+ person = Person.new
71
+ person.name = 1
72
+ assert(person.name.is_a? String)
73
+ end
74
+
75
+ def test_boolean_typecasting
76
+ person = Person.new
77
+
78
+ person.married = 'false'
79
+ assert(person.married == false)
80
+
81
+ person.married = 'FALSE'
82
+ assert(person.married == false)
83
+
84
+ person.married = '0'
85
+ assert(person.married == false)
86
+
87
+ person.married = 'foo'
88
+ assert(person.married == true)
89
+
90
+ person.married = 0
91
+ assert(person.married == false)
92
+
93
+ person.married = 10
94
+ assert(person.married == true)
95
+ end
96
+
97
+ def test_defaults
98
+ person = Person.new
99
+ assert_equal(false, person.married)
100
+ assert_equal(nil, person.name)
101
+ assert_equal(nil, person.spouse)
102
+ assert_equal([], person.children)
103
+ end
104
+
105
+ def test_array
106
+ person = Person.new
107
+ child = Person.new
108
+ person.children << child
109
+ assert_equal(1, person.children.count)
110
+ assert_equal(0, child.children.count)
111
+ end
112
+
113
+ def test_json
114
+ person = Person.new(:name => 'Joe')
115
+ json = person.to_json
116
+ assert_equal(person, JSON.parse(json))
117
+ end
118
+
119
+ def test_json_with_nested_structures
120
+ person = Person.new
121
+ person.children << Person.new
122
+ json = person.to_json
123
+ assert(JSON.parse(json).children.first.is_a? Person)
124
+ end
125
+
126
+ def test_json_with_active_support
127
+ require 'active_support/ordered_hash'
128
+ require 'active_support/json'
129
+
130
+ person = Person.new
131
+ assert(person.as_json(:only => :name).has_key?(:name))
132
+ assert(!person.as_json(:except => :name).has_key?(:name))
133
+ end
134
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 12
8
- - 2
9
- version: 0.12.2
7
+ - 13
8
+ - 1
9
+ version: 0.13.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Paper Cavalier
@@ -14,67 +14,11 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-07-25 00:00:00 +01:00
17
+ date: 2011-07-26 00:00:00 +01:00
18
18
  default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
21
- name: activesupport
22
- requirement: &id001 !ruby/object:Gem::Requirement
23
- none: false
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- segments:
28
- - 3
29
- - 0
30
- version: "3.0"
31
- type: :development
32
- prerelease: false
33
- version_requirements: *id001
34
- - !ruby/object:Gem::Dependency
35
- name: rake
36
- requirement: &id002 !ruby/object:Gem::Requirement
37
- none: false
38
- requirements:
39
- - - ~>
40
- - !ruby/object:Gem::Version
41
- segments:
42
- - 0
43
- - 9
44
- version: "0.9"
45
- type: :development
46
- prerelease: false
47
- version_requirements: *id002
48
- - !ruby/object:Gem::Dependency
49
- name: rspec
50
- requirement: &id003 !ruby/object:Gem::Requirement
51
- none: false
52
- requirements:
53
- - - ~>
54
- - !ruby/object:Gem::Version
55
- segments:
56
- - 2
57
- - 6
58
- version: "2.6"
59
- type: :development
60
- prerelease: false
61
- version_requirements: *id003
62
- - !ruby/object:Gem::Dependency
63
- name: ruby-debug19
64
- requirement: &id004 !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ~>
68
- - !ruby/object:Gem::Version
69
- segments:
70
- - 0
71
- - 11
72
- - 6
73
- version: 0.11.6
74
- type: :development
75
- prerelease: false
76
- version_requirements: *id004
77
- description: Structure is a key/value container for modeling ephemeral data.
19
+ dependencies: []
20
+
21
+ description: Structure turns a class into a key/value container.
78
22
  email:
79
23
  - code@papercavalier.com
80
24
  executables: []
@@ -86,28 +30,19 @@ extra_rdoc_files: []
86
30
  files:
87
31
  - .gitignore
88
32
  - .rvmrc
33
+ - .travis.yml
89
34
  - CHANGELOG.md
90
35
  - Gemfile
91
36
  - LICENSE
92
37
  - README.md
93
38
  - Rakefile
94
- - benchmark.rb
95
39
  - lib/structure.rb
96
- - lib/structure/static.rb
97
40
  - lib/structure/version.rb
98
- - spec/fixtures/cities.yml
99
- - spec/fixtures/cities_with_neighborhoods.yml
100
- - spec/fixtures/cities_without_id.yml
101
- - spec/models/book.rb
102
- - spec/models/city.rb
103
- - spec/models/person.rb
104
- - spec/spec_helper.rb
105
- - spec/structure/json_spec.rb
106
- - spec/structure/static_spec.rb
107
- - spec/structure_spec.rb
108
41
  - structure.gemspec
42
+ - test/models.rb
43
+ - test/structure_test.rb
109
44
  has_rdoc: true
110
- homepage: http://rubygems.com/gems/structure
45
+ homepage: http://code.papercavalier.com/structure
111
46
  licenses: []
112
47
 
113
48
  post_install_message:
@@ -120,7 +55,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
120
55
  requirements:
121
56
  - - ">="
122
57
  - !ruby/object:Gem::Version
123
- hash: 2262708410600119149
58
+ hash: -2785152293474576694
124
59
  segments:
125
60
  - 0
126
61
  version: "0"
@@ -129,7 +64,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
64
  requirements:
130
65
  - - ">="
131
66
  - !ruby/object:Gem::Version
132
- hash: 2262708410600119149
133
67
  segments:
134
68
  - 0
135
69
  version: "0"
@@ -139,15 +73,7 @@ rubyforge_project: structure
139
73
  rubygems_version: 1.3.7
140
74
  signing_key:
141
75
  specification_version: 3
142
- summary: A key/value container for modeling ephemeral data
76
+ summary: Turns a class into a key/value container
143
77
  test_files:
144
- - spec/fixtures/cities.yml
145
- - spec/fixtures/cities_with_neighborhoods.yml
146
- - spec/fixtures/cities_without_id.yml
147
- - spec/models/book.rb
148
- - spec/models/city.rb
149
- - spec/models/person.rb
150
- - spec/spec_helper.rb
151
- - spec/structure/json_spec.rb
152
- - spec/structure/static_spec.rb
153
- - spec/structure_spec.rb
78
+ - test/models.rb
79
+ - test/structure_test.rb
data/benchmark.rb DELETED
@@ -1,24 +0,0 @@
1
- require 'benchmark'
2
- require 'ostruct'
3
- require_relative 'lib/structure'
4
-
5
- class Person1 < Structure
6
- key :name
7
- end
8
-
9
- class Person2 < Struct.new(:name)
10
- end
11
-
12
- class Person3
13
- attr_accessor :name
14
- end
15
-
16
- n = 100000
17
- Benchmark.bm do |x|
18
- x.report('Structure') { n.times { Person1.new(:name => 'John') } }
19
- x.report('Structure') { n.times { Person1.new.name = 'John' } }
20
- x.report('OpenStruct') { n.times { OpenStruct.new(:name => 'John') } }
21
- x.report('Struct') { n.times { Person2.new('John') } }
22
- x.report('Class') { n.times { Person3.new.name = 'John' } }
23
- x.report('Hash') { n.times { { name: 'John' } } }
24
- end
@@ -1,52 +0,0 @@
1
- require 'yaml'
2
-
3
- class Structure
4
-
5
- # An enumerable static structure, sourced from a yaml file.
6
- module Static
7
- def self.included(base)
8
- base.key(:id, Integer)
9
- base.extend(ClassMethods)
10
- end
11
-
12
- module ClassMethods
13
- include Enumerable
14
-
15
- attr_accessor :data_path
16
-
17
- def all
18
- @all ||= data.map do |record|
19
- record["id"] ||= increment_id
20
- new(record)
21
- end
22
- end
23
-
24
- def each(&block)
25
- all.each { |record| block.call(record) }
26
- end
27
-
28
- def find(id)
29
- detect { |record| record.id == id }
30
- end
31
-
32
- def set_data_path(data_path)
33
- @data_path = data_path
34
- end
35
-
36
- private
37
-
38
- def data
39
- YAML.load_file(data_path)
40
- end
41
-
42
- # Overwrite this method with an opiniated location to dry if necessary.
43
- def data_path
44
- @data_path
45
- end
46
-
47
- def increment_id
48
- @increment_id = @increment_id.to_i + 1
49
- end
50
- end
51
- end
52
- end
@@ -1,5 +0,0 @@
1
- ---
2
- - id: 1
3
- name: New York
4
- - id: 2
5
- name: Tokyo
@@ -1,7 +0,0 @@
1
- ---
2
- - id: 1
3
- name: New York
4
- neighborhoods:
5
- - name: Manhattan
6
- - name: Brooklyn
7
- - name: Queens
@@ -1,4 +0,0 @@
1
- ---
2
- - name: New York
3
- - name: Tokyo
4
- - name: Paris
data/spec/models/city.rb DELETED
@@ -1,14 +0,0 @@
1
- require 'structure/static'
2
-
3
- class City < Structure
4
- include Static
5
-
6
- set_data_path File.expand_path("../../fixtures/cities.yml", __FILE__)
7
-
8
- key :name
9
- embeds_many :neighborhoods
10
- end
11
-
12
- class Neighborhood < Structure
13
- key :name
14
- end
@@ -1,5 +0,0 @@
1
- class Person < Structure
2
- key :name
3
- key :age, Integer
4
- embeds_many :friends
5
- end
data/spec/spec_helper.rb DELETED
@@ -1,7 +0,0 @@
1
- require "rubygems"
2
- require "bundler/setup"
3
- require "rspec"
4
-
5
- require_relative "../lib/structure"
6
-
7
- Dir["#{File.dirname(__FILE__)}/models/**/*.rb"].each { |f| require f }
@@ -1,62 +0,0 @@
1
- require 'spec_helper'
2
-
3
- shared_examples_for "a JSON interface" do
4
- it "dumps to JSON" do
5
- person.to_json.should eql json
6
- end
7
-
8
- it "loads from JSON" do
9
- JSON.parse(json).should == person
10
- end
11
-
12
- context "when nesting other structures" do
13
- before do
14
- person.friends = [Person.new(:name => 'Jane')]
15
- end
16
-
17
- it "loads them into their corresponding structures" do
18
- json = person.to_json
19
- JSON.parse(json).friends.first.should be_a Person
20
- end
21
- end
22
- end
23
-
24
- describe Structure do
25
- let(:person) { Person.new(:name => 'Joe') }
26
- let(:json) { '{"json_class":"Person","name":"Joe","age":null,"friends":[]}' }
27
-
28
- context "without Active Support" do
29
- it_behaves_like "a JSON interface"
30
- end
31
-
32
- context "with Active Support" do
33
- before(:all) do
34
- require 'active_support/ordered_hash'
35
- require 'active_support/json'
36
- end
37
-
38
- after(:all) do
39
- Object.send(:remove_const, :ActiveSupport)
40
- end
41
-
42
- it_behaves_like "a JSON interface"
43
-
44
- describe "#as_json" do
45
- it "returns a hash" do
46
- person.as_json.should be_a Hash
47
- end
48
-
49
- it "selects a subset of attributes" do
50
- as_json = person.as_json(:only => :name)
51
- as_json.should have_key :name
52
- as_json.should_not have_key :age
53
- end
54
-
55
- it "rejects a subset of attributes" do
56
- as_json = person.as_json(:except => :name)
57
- as_json.should_not have_key :name
58
- as_json.should have_key :age
59
- end
60
- end
61
- end
62
- end
@@ -1,85 +0,0 @@
1
- require 'spec_helper'
2
-
3
- class Structure
4
- describe "A static structure" do
5
- def replace_fixture(new_path)
6
- City.instance_variable_set(:@all, nil)
7
- fixture = File.expand_path("../../fixtures/#{new_path}", __FILE__)
8
- City.set_data_path(fixture)
9
- end
10
-
11
- it "is enumerable" do
12
- City.should be_an Enumerable
13
- end
14
-
15
- describe ".all" do
16
- it "returns all records" do
17
- City.all.should have(2).cities
18
- end
19
- end
20
-
21
- describe ".find" do
22
- it "finds a record by its id" do
23
- City.find(1).should be_a City
24
- end
25
-
26
- it "returns nil if the record does not exist" do
27
- City.find(4).should be_nil
28
- end
29
- end
30
-
31
- describe ".set_data_path" do
32
- it "sets the data path" do
33
- City.set_data_path("foo")
34
- City.send(:data_path).should eql "foo"
35
- end
36
- end
37
-
38
- context "when sourcing data without ids" do
39
- before(:all) do
40
- @old_path = City.instance_variable_get(:@data_path)
41
- replace_fixture("cities_without_id.yml")
42
- end
43
-
44
- after(:all) do
45
- replace_fixture(@old_path)
46
- end
47
-
48
- it "should auto-increment the ids of loaded records" do
49
- City.map(&:id).should =~ [1, 2, 3]
50
- end
51
-
52
- describe ".increment_id" do
53
- before do
54
- City.instance_variable_set(:@increment_id, nil)
55
- end
56
-
57
- it "starts from 1" do
58
- City.send(:increment_id).should eql 1
59
- end
60
-
61
- it "auto-increments" do
62
- City.send(:increment_id)
63
- City.send(:increment_id).should eql 2
64
- end
65
- end
66
- end
67
-
68
- context "when sourcing nested models" do
69
- before(:all) do
70
- @old_path = City.instance_variable_get(:@data_path)
71
- replace_fixture("cities_with_neighborhoods.yml")
72
- end
73
-
74
- after(:all) do
75
- replace_fixture(@old_path)
76
- end
77
-
78
- it "loads nested models" do
79
- pending
80
- neighborhoods = City.first.neighborhoods
81
- neighborhoods.first.should be_a Neighborhood
82
- end
83
- end
84
- end
85
- end
@@ -1,221 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Structure do
4
- let(:person) { Person.new }
5
-
6
- it "is enumerable" do
7
- person.should respond_to :map
8
- end
9
-
10
- describe ".key" do
11
- it "defines accessors" do
12
- %w{name name=}.each { |method| person.should respond_to method }
13
- end
14
-
15
- context "when a key name clashes with a method name" do
16
- it "raises an error" do
17
- expect do
18
- Person.key :class
19
- end.to raise_error NameError
20
- end
21
- end
22
-
23
- context "when an invalid type is specified" do
24
- it "raises an error" do
25
- expect do
26
- Person.key :location, Object
27
- end.to raise_error TypeError
28
- end
29
- end
30
-
31
- context "when default value is not of the specified type" do
32
- it "raises an error" do
33
- expect do
34
- Person.key :location, String, :default => 0
35
- end.to raise_error TypeError
36
- end
37
- end
38
- end
39
-
40
- describe ".default_attributes" do
41
- it "returns the default attributes for the structure" do
42
- Person.send(:default_attributes).should == { :name => nil,
43
- :age => nil,
44
- :friends => [] }
45
- Book.send(:default_attributes).should == { :title => nil,
46
- :authors => nil }
47
- end
48
- end
49
-
50
- describe "attribute getter" do
51
- it "returns the value of the attribute" do
52
- person.instance_variable_get(:@attributes)[:name] = 'Joe'
53
- person.name.should eql 'Joe'
54
- end
55
-
56
- context "when type is Array and default value is []" do
57
- let(:friend) { Person.new }
58
-
59
- it "supports the `<<' idiom" do
60
- person.friends << friend
61
- person.friends.count.should eql 1
62
- friend.friends.count.should eql 0
63
- end
64
- end
65
- end
66
-
67
- describe "attribute setter" do
68
- it "sets the value of the attribute" do
69
- person.name = "Joe"
70
- person.instance_variable_get(:@attributes)[:name].should eql 'Joe'
71
- end
72
-
73
- context "when a type is specified" do
74
- it "casts the value" do
75
- person.age = "28"
76
- person.age.should be_an Integer
77
- end
78
- end
79
-
80
- context "when a type is not specified" do
81
- it "casts to String" do
82
- person.name = 123
83
- person.name.should be_a String
84
- end
85
- end
86
-
87
- context "when type is Boolean" do
88
- context "when default value is true" do
89
- it "does not raise an invalid type error" do
90
- expect do
91
- Person.key :single, Boolean, :default => true
92
- end.not_to raise_error
93
- end
94
- end
95
-
96
- context "when default value is false" do
97
- it "does not raise an invalid type error" do
98
- expect do
99
- Person.key :married, Boolean, :default => false
100
- end.not_to raise_error
101
- end
102
- end
103
-
104
- context "when typecasting a set value" do
105
- before(:all) do
106
- Person.key :vegetarian, Boolean
107
- end
108
-
109
- it "typecasts 'true' to true" do
110
- person.vegetarian = 'true'
111
- person.vegetarian.should be_true
112
- end
113
-
114
- it "typecasts 'TRUE' to true" do
115
- person.vegetarian = 'TRUE'
116
- person.vegetarian.should be_true
117
- end
118
-
119
- it "typecasts '1' to true" do
120
- person.vegetarian = '1'
121
- person.vegetarian.should be_true
122
- end
123
-
124
- it "typecasts all other strings to false" do
125
- person.vegetarian = 'foo'
126
- person.vegetarian.should be_false
127
- end
128
-
129
- it "typecasts 0 to false" do
130
- person.vegetarian = 0
131
- person.vegetarian.should be_false
132
- end
133
-
134
- it "typecasts all other integers to true" do
135
- person.vegetarian = 1
136
- person.vegetarian.should be_true
137
- end
138
- end
139
- end
140
-
141
- context "when type is Hash" do
142
- before(:all) do
143
- Person.key :education, Hash
144
- end
145
-
146
- context "when setting to a value that is not a Hash" do
147
- it "raises an error" do
148
- expect do
149
- person.education = 'foo'
150
- end.to raise_error TypeError
151
- end
152
- end
153
- end
154
-
155
- context "when type is Structure" do
156
- before(:all) do
157
- Person.embeds_one :father
158
- end
159
-
160
- context "when setting to a value that is not a Structure" do
161
- it "raises an error" do
162
- expect do
163
- person.father = 'foo'
164
- end.to raise_error TypeError
165
- end
166
- end
167
- end
168
-
169
- context "when a default is specified" do
170
- it "defaults to that value" do
171
- Person.key :location, :default => 'New York'
172
- person.location.should eql 'New York'
173
- end
174
- end
175
-
176
- context "when a default is not specified" do
177
- it "defaults to nil" do
178
- person.age.should be_nil
179
- end
180
- end
181
-
182
- context "when setting the value of an attribute to nil" do
183
- it "does not typecast the value" do
184
- person.age = nil
185
- person.age.should be_a NilClass
186
- end
187
- end
188
- end
189
-
190
- describe "attribute presence" do
191
- context "when the value of an attribute is nil" do
192
- it "returns false" do
193
- person.name?.should be_false
194
- end
195
- end
196
-
197
- context "when the value of an attribute is false" do
198
- it "returns false" do
199
- person.instance_variable_get(:@attributes)[:name] = false
200
- person.name?.should be_false
201
- end
202
- end
203
-
204
- context "when the value of an attribute is set to a value other than false or nil" do
205
- it "returns true" do
206
- person.instance_variable_get(:@attributes)[:name] = "John"
207
- person.name?.should be_true
208
- end
209
- end
210
- end
211
-
212
- describe ".new" do
213
- context "when attributes are specified" do
214
- it "initializes the object with those attributes" do
215
- jane = Person.new(:name => 'Jane', :age => "29")
216
- jane.name.should eql 'Jane'
217
- jane.age.should eql 29
218
- end
219
- end
220
- end
221
- end