structure 0.12.2 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
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