structure 0.1.0 → 0.2.0

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,5 +1,4 @@
1
1
  pkg/*
2
2
  *.gem
3
3
  .bundle
4
- .rvmrc
5
4
  Gemfile.lock
data/README.md CHANGED
@@ -1,25 +1,62 @@
1
1
  Structure
2
2
  =========
3
3
 
4
- Structure is a nested OpenStruct implementation. Or, recursively put, Structure is a truly OpenStruct OpenStruct.
4
+ Structure is a better struct.
5
+
6
+
7
+ #_ d
8
+ ##_ d#
9
+ NN#p j0NN
10
+ 40NNh_ _gN#B0
11
+ 4JF@NNp_ _g0WNNL@
12
+ JLE5@WRNp_ _g@NNNF3_L
13
+ _F`@q4WBN@Np_ _gNN@ZL#p"Fj_
14
+ "0^#-LJ_9"NNNMp__ _gN#@#"R_#g@q^9"
15
+ a0,3_j_j_9FN@N@0NMp__ __ggNZNrNM"P_f_f_E,0a
16
+ j L 6 9""Q"#^q@NDNNNMpg____ ____gggNNW#W4p^p@jF"P"]"j F
17
+ rNrr4r*pr4r@grNr@q@Ng@q@N0@N#@NNMpmggggmqgNN@NN@#@4p*@M@p4qp@w@m@Mq@r#rq@r
18
+ F Jp 9__b__M,Juw*w*^#^9#""EED*dP_@EZ@^E@*#EjP"5M"gM@p*Ww&,jL_J__f F j
19
+ -r#^^0""E" 6 q q__hg-@4""*,_Z*q_"^pwr""p*C__@""0N-qdL_p" p J" 3""5^^0r-
20
+ t J __,Jb--N""", *_s0M`""q_a@NW__JP^u_p"""p4a,p" _F""V--wL,_F_ F #
21
+ _,Jp*^#""9 L 5_a*N"""q__INr" "q_e^"*,p^""qME_ y"""p6u,f j' f "N^--LL_
22
+ L ] k,w@#"""_ "_a*^E ba-" ^qj-""^pe" J^-u_f _f "q@w,j f jL
23
+ #_,J@^""p `_ _jp-""q _Dw^" ^cj*""*,j^ "p#_ y""^wE_ _F F"^qN,_j
24
+ w*^0 4 9__sAF" `L _Dr" m__m""q__a^"m__* "qA_ j" ""Au__f J 0^--
25
+ ] J_,x-E 3_ jN^" `u _w^*_ _RR_ _J^w_ j" "pL_ f 7^-L_F #
26
+ jLs*^6 `_ _&*" q _,NF "wp" "*g" _NL_ p "-d_ F ]"*u_F
27
+ ,x-"F ] Ax^" q hp" `u jM""u a^ ^, j" "*g_ p ^mg_ D.H. 1992
28
+
29
+
30
+ Usage
31
+ -----
32
+
33
+ Structure, like Struct, is great for defining ephemeral models.
5
34
 
6
35
  require 'structure'
7
-
8
- source = {
9
- :title => 'Mille Plateaux',
10
- :authors => [
11
- {
12
- :name => "Deleuze",
13
- }
14
- ],
15
- :publisher => {
16
- :name => "Minuit",
17
- }
18
-
19
- book = Structure.new(source)
20
-
21
- puts book.authors.first.name
22
- => "Gilles Deleuze"
23
-
24
- puts book.publisher.name
25
- => "Minuit"
36
+
37
+ class Person < Structure
38
+ key :name
39
+ key :age, :type => Integer
40
+ key :friends, :type => Array
41
+ end
42
+
43
+ john = Person.new(:name => "John",
44
+ :age => 28)
45
+
46
+ jane = Person.new(:name => "Jane",
47
+ :age => 24)
48
+
49
+ john.friends = [jane]
50
+
51
+ When it comes to dumping JSON, Structure is more aesthetically-minded.
52
+
53
+ require 'structure/json'
54
+
55
+ json = john.to_json
56
+ => {"json_class":"Person","name":"John","age":28,"friends":[{"json_class":"Person","name":"Jane","age":24,"friends":null}]}
57
+
58
+ person = JSON.parse(json)
59
+ person.friends.first.name
60
+ => "Jane"
61
+ person.friends.first.age
62
+ => 24
data/Rakefile CHANGED
@@ -1,2 +1,13 @@
1
1
  require 'bundler'
2
+ require 'cucumber/rake/task'
3
+ require 'rspec/core/rake_task'
4
+
2
5
  Bundler::GemHelper.install_tasks
6
+
7
+ desc 'Run all specs in spec directory'
8
+ RSpec::Core::RakeTask.new(:spec) do |t|
9
+ t.pattern = "spec/**/*_spec.rb"
10
+ t.rspec_opts = %w(-fd -c)
11
+ end
12
+
13
+ task :default => [:spec]
data/lib/structure.rb CHANGED
@@ -1,44 +1,96 @@
1
- require 'ostruct'
2
-
3
- #
4
- # Structure is a nested OpenStruct implementation.
5
- #
6
- class Structure < OpenStruct
7
- def initialize(source = {})
8
- @table = {}
9
- source.each do |k, v|
10
- @table[k.to_sym] = structure(v)
11
- new_ostruct_member(k)
1
+ require 'json'
2
+
3
+ # A better struct.
4
+ class Structure
5
+
6
+ # Mix in the Enumerable module.
7
+ include Enumerable
8
+
9
+ @@keys = []
10
+
11
+ # Defines an attribute key.
12
+ #
13
+ # Takes a name and an optional hash of options. Available options are:
14
+ #
15
+ # * :type, which can be Integer, Float, String, and Array.
16
+ #
17
+ # class Book
18
+ # key :title, :type => String
19
+ # key :authors, :type => Array
20
+ # end
21
+ #
22
+ def self.key(name, options={})
23
+ if method_defined?(name)
24
+ raise NameError, "#{name} is already defined"
12
25
  end
13
- end
14
26
 
15
- def method_missing(mid, *args) # :nodoc:
16
- mname = mid.id2name
17
- len = args.length
18
- if mname.chomp!('=')
19
- if len != 1
20
- raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
27
+ name = name.to_sym
28
+ type = options[:type]
29
+ @@keys << name
30
+
31
+ module_eval do
32
+
33
+ # Define a getter.
34
+ define_method(name) { @attributes[name] }
35
+
36
+ # Define a setter. The setter will optionally typecast.
37
+ define_method("#{name}=") do |value|
38
+ modifiable[name] =
39
+ if type && value
40
+ Kernel.send(type.to_s, value)
41
+ else
42
+ value
43
+ end
21
44
  end
22
- modifiable[new_ostruct_member(mname)] = structure(args[0])
23
- elsif len == 0
24
- @table[mid]
25
- else
26
- raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1)
27
45
  end
28
46
  end
29
47
 
30
- alias :to_hash :marshal_dump
48
+ # Creates a new structure.
49
+ #
50
+ # Optionally, populates the structure with a hash of attributes. Otherwise,
51
+ # all values default to nil.
52
+ def initialize(seed = {})
53
+ @attributes =
54
+ @@keys.inject({}) do |attributes, name|
55
+ attributes[name] = nil
56
+ attributes
57
+ end
58
+
59
+ seed.each { |key, value| self.send("#{key}=", value) }
60
+ end
61
+
62
+ # A hash of attributes.
63
+ attr_reader :attributes
64
+
65
+ def each(&block)
66
+ @attributes.each { |value| block.call(value) }
67
+ end
68
+
69
+ # Returns an array populated with the attribute keys.
70
+ def keys
71
+ @attributes.keys
72
+ end
73
+
74
+ # Returns an array populated with the attribute values.
75
+ def values
76
+ @attributes.values
77
+ end
78
+
79
+ # Compares this object with another object for equality. A Structure is equal
80
+ # to the other object when latter is also a Structure and the two objects'
81
+ # attributes are equal.
82
+ def ==(other)
83
+ other.is_a?(Structure) && @attributes == other.attributes
84
+ end
31
85
 
32
86
  private
33
87
 
34
- def structure(o)
35
- case o
36
- when Hash
37
- self.class.new(o)
38
- when Array
39
- o.map { |o| structure(o) }
40
- else
41
- o
88
+ def modifiable
89
+ begin
90
+ @modifiable = true
91
+ rescue
92
+ raise TypeError, "can't modify frozen #{self.class}", caller(3)
42
93
  end
94
+ @attributes
43
95
  end
44
96
  end
@@ -0,0 +1,18 @@
1
+ unless Object.const_defined?(:JSON) and ::JSON.const_defined?(:JSON_LOADED) and
2
+ ::JSON::JSON_LOADED
3
+ require 'json'
4
+ end
5
+
6
+ class Structure
7
+ def self.json_create(object)
8
+ object.delete('json_class')
9
+ new(object)
10
+ end
11
+
12
+ def to_json(*args)
13
+ klass = self.class.name
14
+ { JSON.create_id => klass }.
15
+ merge(@attributes).
16
+ to_json(*args)
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ class Sucker
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,5 @@
1
+ class Person < Structure
2
+ key :name
3
+ key :age, :type => Integer
4
+ key :friends
5
+ end
@@ -0,0 +1,7 @@
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 }
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Structure do
4
+ context "when `structure/json' is required" do
5
+ let(:person) { Person.new(:name => 'Joe', :age => 28) }
6
+ let(:json) { '{"json_class":"Person","name":"Joe","age":28,"friends":null}' }
7
+
8
+ before do
9
+ require 'structure/json'
10
+ end
11
+
12
+ it "dumps to JSON" do
13
+ person.to_json.should eql json
14
+ end
15
+
16
+ it "loads from JSON" do
17
+ JSON.parse(json).should == person
18
+ end
19
+
20
+ context "when nesting other structures" do
21
+ before do
22
+ person.friends = [Person.new(:name => 'Jane')]
23
+ end
24
+
25
+ it "loads nested structures from JSON" do
26
+ json = person.to_json
27
+ JSON.parse(json).friends.first.name.should eql 'Jane'
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe Structure do
4
+ let(:person) { Person.new }
5
+
6
+ it "is enumerable" do
7
+ person.name ="Joe"
8
+ person.map { |key, value| value }.should include "Joe"
9
+ end
10
+
11
+ context "when object is frozen" do
12
+ before do
13
+ person.freeze
14
+ end
15
+
16
+ it "raises an error" do
17
+ expect do
18
+ person.name = 'Joe'
19
+ end.to raise_error TypeError
20
+ end
21
+ end
22
+
23
+ describe ".key" do
24
+ it "defines accessors" do
25
+ %w{name name=}.each { |method| person.should respond_to method }
26
+ end
27
+
28
+ context "when name clashes with an existing method" do
29
+ it "raises an error" do
30
+ expect do
31
+ Person.key :name
32
+ end.to raise_error NameError
33
+ end
34
+ end
35
+
36
+ context "when a type is specified" do
37
+ context "when setting the attribute to a non-nil value" do
38
+ it "casts the value" do
39
+ person.age = "28"
40
+ person.age.should eql 28
41
+ end
42
+ end
43
+
44
+ context "when setting the attribute to nil" do
45
+ it "does not set the value" do
46
+ person.age = nil
47
+ person.age.should be_nil
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ describe ".new" do
54
+ context "when attributes are specified" do
55
+ it "initializes the object with those attributes" do
56
+ jane = Person.new(:name => 'Jane', :age => "29")
57
+ jane.name.should eql 'Jane'
58
+ jane.age.should eql 29
59
+ end
60
+ end
61
+ end
62
+ end
data/structure.gemspec CHANGED
@@ -1,16 +1,31 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'structure/version'
4
+
2
5
  Gem::Specification.new do |s|
3
6
  s.name = "structure"
4
- s.version = "0.1.0"
7
+ s.version = Sucker::VERSION
5
8
  s.platform = Gem::Platform::RUBY
6
9
  s.authors = ["Paper Cavalier"]
7
10
  s.email = ["code@papercavalier.com"]
8
- s.homepage = ""
9
- s.summary = %q{Structure is a nested OpenStruct implementation.}
10
- s.description = %q{A nested OpenStruct implementation}
11
+ s.homepage = "http://rubygems.com/gems/structure"
12
+ s.summary = "Structure is a better Struct."
13
+ s.description = <<-END_OF_DESCRIPTION.strip
14
+ Structure is a better Struct.
15
+
16
+ Like Struct, it is great for setting up ephemeral models. It also handles
17
+ typecasting and, unlike Struct, dumps nicely-formatted JSON.
18
+ END_OF_DESCRIPTION
11
19
 
12
20
  s.rubyforge_project = "structure"
13
21
 
22
+ {
23
+ 'rspec' => '~> 2.6.0',
24
+ 'ruby-debug19' => '~> 0.11.6'
25
+ }.each do |lib, version|
26
+ s.add_development_dependency lib, version
27
+ end
28
+
14
29
  s.files = `git ls-files`.split("\n")
15
30
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
31
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
metadata CHANGED
@@ -1,8 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: structure
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.1.0
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
6
10
  platform: ruby
7
11
  authors:
8
12
  - Paper Cavalier
@@ -10,11 +14,44 @@ autorequire:
10
14
  bindir: bin
11
15
  cert_chain: []
12
16
 
13
- date: 2011-02-09 00:00:00 +00:00
17
+ date: 2011-05-26 00:00:00 +01:00
14
18
  default_executable:
15
- dependencies: []
16
-
17
- description: A nested OpenStruct implementation
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 2
30
+ - 6
31
+ - 0
32
+ version: 2.6.0
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: ruby-debug19
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ - 11
46
+ - 6
47
+ version: 0.11.6
48
+ type: :development
49
+ version_requirements: *id002
50
+ description: |-
51
+ Structure is a better Struct.
52
+
53
+ Like Struct, it is great for setting up ephemeral models. It also handles
54
+ typecasting and, unlike Struct, dumps nicely-formatted JSON.
18
55
  email:
19
56
  - code@papercavalier.com
20
57
  executables: []
@@ -29,10 +66,15 @@ files:
29
66
  - README.md
30
67
  - Rakefile
31
68
  - lib/structure.rb
69
+ - lib/structure/json.rb
70
+ - lib/structure/version.rb
71
+ - spec/models/person.rb
72
+ - spec/spec_helper.rb
73
+ - spec/structure/json_spec.rb
74
+ - spec/structure_spec.rb
32
75
  - structure.gemspec
33
- - test/structure_test.rb
34
76
  has_rdoc: true
35
- homepage: ""
77
+ homepage: http://rubygems.com/gems/structure
36
78
  licenses: []
37
79
 
38
80
  post_install_message:
@@ -45,19 +87,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
87
  requirements:
46
88
  - - ">="
47
89
  - !ruby/object:Gem::Version
90
+ segments:
91
+ - 0
48
92
  version: "0"
49
93
  required_rubygems_version: !ruby/object:Gem::Requirement
50
94
  none: false
51
95
  requirements:
52
96
  - - ">="
53
97
  - !ruby/object:Gem::Version
98
+ segments:
99
+ - 0
54
100
  version: "0"
55
101
  requirements: []
56
102
 
57
103
  rubyforge_project: structure
58
- rubygems_version: 1.5.0
104
+ rubygems_version: 1.3.7
59
105
  signing_key:
60
106
  specification_version: 3
61
- summary: Structure is a nested OpenStruct implementation.
107
+ summary: Structure is a better Struct.
62
108
  test_files:
63
- - test/structure_test.rb
109
+ - spec/models/person.rb
110
+ - spec/spec_helper.rb
111
+ - spec/structure/json_spec.rb
112
+ - spec/structure_spec.rb
@@ -1,46 +0,0 @@
1
- require 'minitest/spec'
2
- require File.expand_path('../../lib/structure', __FILE__)
3
-
4
- MiniTest::Unit.autorun
5
-
6
- describe Structure do
7
- before do
8
- @hash = {
9
- :name => "John",
10
- :children => [ { :name => "Jim" } ],
11
- :location => { :city => { :name => "London" } }
12
- }
13
- end
14
-
15
- describe ".new" do
16
- before do
17
- @person = Structure.new(@hash)
18
- end
19
-
20
- it "structures nested hashes" do
21
- @person.location.city.name.must_equal "London"
22
- end
23
-
24
- it "structures hashes in arrays" do
25
- @person.children.must_be_instance_of Array
26
- @person.children.first.name.must_equal "Jim"
27
- end
28
- end
29
-
30
- describe "#=" do
31
- before do
32
- @person = Structure.new
33
- end
34
-
35
- it "structures nested hashes" do
36
- @person.location = { :city => { :name => "London" } }
37
- @person.location.city.name.must_equal "London"
38
- end
39
-
40
- it "structures hashes in arrays" do
41
- @person.children = [ { :name => "Jim" } ]
42
- @person.children.must_be_instance_of Array
43
- @person.children.first.name.must_equal "Jim"
44
- end
45
- end
46
- end