collabda 0.0.1a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/collabda.rb +113 -0
  3. data/spec/lib/collabda_spec.rb +179 -0
  4. metadata +60 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 72411f5cfca47c38d7810c6d0619bfb68fda266a
4
+ data.tar.gz: 5c73e2d894bbb0d1fbb64c7c527686a4826873f9
5
+ SHA512:
6
+ metadata.gz: 647e46c9f8395fd4e99d9534764073ec36408e77dc3663fc9af315872081056678ca2da94c0f41c614169cc78a908ff8dcb699fc6adcba41f0772973dfbc0255
7
+ data.tar.gz: 762fa5e7c1d0e82ca7937b9d13330c2667c44bdd0147ba78cc5aaadfc58b4af65a0e14de5f9de1c6512218cb3650f9ed4153ce5680354254df8925f32e2ffb5a
data/lib/collabda.rb ADDED
@@ -0,0 +1,113 @@
1
+ module Collabda
2
+ def self.included(base)
3
+ base.extend(Enumerable)
4
+ base.extend(ClassMethods)
5
+ @classes ||= []
6
+ @classes << base
7
+ end
8
+
9
+ def self.rebuild_collections
10
+ return if @classes.nil?
11
+ @classes.each do |c|
12
+ c.build_collection
13
+ end
14
+ end
15
+
16
+ def self.collection(class_name, &block)
17
+ model = Class.new do
18
+ include Collabda
19
+ self.class_eval(&block)
20
+ end
21
+ nesting = block.binding.eval("Module.nesting[0]") || Object
22
+ nesting.instance_eval{const_set(class_name, model)}
23
+ model.build_collection
24
+ return model
25
+ end
26
+
27
+ # def self.watch_files
28
+ # @classes.map{|c| c.source_path}
29
+ # end
30
+
31
+ InvalidSource = Class.new(StandardError)
32
+ MissingAttributes = Class.new(StandardError)
33
+
34
+ module ClassMethods
35
+ def source(path, options={})
36
+ @source_path = path
37
+ @format = options[:type] || :yaml
38
+ fetch_data
39
+ end
40
+
41
+ def all
42
+ @collabda_models
43
+ end
44
+
45
+ def each(&block)
46
+ all.each(&block)
47
+ end
48
+
49
+ def check_validity
50
+ raise InvalidSource if @source_path.nil?
51
+ raise MissingAttributes if @properties.nil?
52
+ end
53
+
54
+ def build_collection
55
+ check_validity
56
+ fetch_data
57
+ @collabda_models = @parsed_data.map do |el|
58
+ build(el)
59
+ end
60
+ end
61
+
62
+ def build(attributes_hash)
63
+ model = self.new
64
+ @properties.each do |attribute|
65
+ model.instance_variable_set("@#{attribute}",attributes_hash[attribute])
66
+ end
67
+ model
68
+ end
69
+
70
+ def properties(*attributes)
71
+ @properties=attributes
72
+ end
73
+
74
+ def source_path
75
+ @source_path
76
+ end
77
+
78
+ def parsed_data
79
+ @parsed_data
80
+ end
81
+
82
+ private
83
+ def fetch_data
84
+ io = File.open(@source_path)
85
+ @parsed_data = Readers.send(@format,io)
86
+ end
87
+ end
88
+
89
+ module Readers
90
+ def self.yaml(io)
91
+ YAML.load(io).map{|model| model.symbolize_keys}
92
+ end
93
+ def self.json(io)
94
+ JSON.load(io).map{|model| model.symbolize_keys}
95
+ end
96
+ end
97
+ end
98
+
99
+ if defined? Rails
100
+ # TODO: add development autoreload code
101
+ else
102
+ class Hash
103
+ def symbolize_keys
104
+ dup.symbolize_keys!
105
+ end
106
+ def symbolize_keys!
107
+ keys.each do |key|
108
+ self[(key.to_sym rescue key) || key] = delete(key)
109
+ end
110
+ self
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,179 @@
1
+ require 'collabda'
2
+
3
+ FOO_YAML = <<-yaml
4
+ - name: Foo
5
+ description: "yo foo"
6
+ - name: Bar
7
+ description: "at the bar"
8
+ - name: Baz
9
+ description: "get some baz"
10
+ yaml
11
+
12
+ FOO_JSON = <<-json
13
+ [
14
+ {"name": "FooJ", "description": "yo foo"},
15
+ {"name": "BarJ", "description": "at the bar"},
16
+ {"name": "BazJ", "description": "get some baz"}
17
+ ]
18
+ json
19
+
20
+ describe "Collabda" do
21
+ before(:each) do
22
+ File.stub(:open){StringIO.open(FOO_YAML)}
23
+ end
24
+
25
+ let(:class_builder) do
26
+ ->(&block) do
27
+ -> do
28
+ Class.new do
29
+ include Collabda
30
+ # include Collabda::Document
31
+ block.call(self)
32
+ attr_reader :description
33
+ properties :name, :description
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ let(:yaml_class_factory) do
40
+ class_builder.call{|c| c.source "spec/lib/foo.yaml", :type=>:yaml}
41
+ end
42
+
43
+
44
+ it "opens specified file during class definition" do
45
+ File.should_receive(:open)
46
+ model_class = yaml_class_factory.call
47
+ end
48
+
49
+ describe "errors" do
50
+ let(:model_class){Class.new{include Collabda}}
51
+ it "raises InvalidSource error if source not set" do
52
+ expect{model_class.build_collection}.to raise_error Collabda::InvalidSource
53
+ end
54
+ it "raises MissingAttributes error if not set" do
55
+ model_class.source "foo.yaml"
56
+ expect{model_class.build_collection}.to raise_error Collabda::MissingAttributes
57
+ end
58
+ end
59
+
60
+ describe "Yaml model" do
61
+ let!(:model_class){yaml_class_factory.call}
62
+ specify "source sets a source_path class variable" do
63
+ expect(model_class.source_path).to be_a String
64
+ end
65
+
66
+ specify "properties sets fields to use" do
67
+ expect(model_class.instance_variable_get(:@properties)).to eq [:name, :description]
68
+ end
69
+
70
+ it "tracks where Collabda is included" do
71
+ FooList = model_class
72
+ expect(Collabda.instance_variable_get(:@classes)).to include FooList
73
+ end
74
+
75
+ # it "maintains a list of yaml files to watch" do
76
+ # expect(Collabda.watch_files).to include "spec/lib/foo.yaml"
77
+ # end
78
+
79
+ it "loads yaml data from file" do
80
+ expect(model_class.parsed_data.count).to eq 3
81
+ end
82
+
83
+ it "uses symbols as attribute keys for consistency" do
84
+ expect(model_class.parsed_data.first.keys.first).to be_a Symbol
85
+ end
86
+
87
+ it "builds a new model for each element in the data" do
88
+ model_class.should_receive(:new).exactly(3).times.and_return(double(:test))
89
+ model_class.build_collection
90
+ end
91
+
92
+ it "reloads the yaml file on build_collection" do
93
+ model_class.should_receive(:fetch_data).and_call_original
94
+ model_class.build_collection
95
+ end
96
+
97
+ it "can build a new instance from an attributes hash, bypassing yaml" do
98
+ instance = model_class.build(:name=>"Bat",:description=>"man")
99
+ expect(instance.description).to eq "man"
100
+ end
101
+
102
+ context "when collection built" do
103
+ before(:each){model_class.build_collection}
104
+ it "it provides access to all models" do
105
+ expect(model_class.all.count).to eq 3
106
+ expect(model_class.all.first.class).to eq model_class
107
+ end
108
+
109
+ it "sets instance variables for each yaml attribute" do
110
+ expect(model_class.all.first.instance_variable_get(:@name)).to eq "Foo"
111
+ end
112
+
113
+ it "implements enumerable correctly" do
114
+ expect(model_class.map{|m| m.instance_variable_get(:@name)}).to eq ["Foo","Bar","Baz"]
115
+ end
116
+
117
+ it "works alongside attr_reader" do
118
+ expect(model_class.first.description).to eq "yo foo"
119
+ end
120
+ end
121
+ end
122
+
123
+ describe "Json model" do
124
+ before(:each) do
125
+ File.stub(:open){StringIO.open(FOO_JSON)}
126
+ end
127
+ let(:json_class_factory) do
128
+ class_builder.call{|c| c.source "http://foo.bar/baz.json", :type=>:json}
129
+ end
130
+ it "parses json into ruby data" do
131
+ model_class = json_class_factory.call
132
+ expect(model_class.parsed_data.first[:name]).to eq "FooJ"
133
+ end
134
+ end
135
+
136
+ describe "collection" do
137
+ let(:built_collection) do
138
+ Collabda.collection(:Food) do
139
+ source ""
140
+ properties :none
141
+ def foo
142
+ end
143
+ def self.bar
144
+ end
145
+ end
146
+ end
147
+
148
+ after(:each){Object.send(:remove_const,:Food) rescue nil}
149
+
150
+ it "creates a class in parent context" do
151
+ built_collection = module TestModels
152
+ class C
153
+ Collabda.collection(:Food){source "";properties :none}
154
+ end
155
+ end
156
+ expect(built_collection.to_s).to eq "TestModels::C::Food"
157
+ end
158
+
159
+ it "creates a class root context if not in module" do
160
+ expect(built_collection.to_s).to eq "Food"
161
+ end
162
+
163
+ it "includes Collabda in the new class" do
164
+ expect(built_collection).to respond_to :source
165
+ end
166
+
167
+ it "executes its block while building the class" do
168
+ expect(built_collection).to respond_to :bar
169
+ end
170
+
171
+ it "executes is block in the correct context" do
172
+ expect(built_collection.new).to respond_to :foo
173
+ end
174
+
175
+ it "auto-builds the collection from its source" do
176
+ expect{Collabda.collection(:Food){}}.to raise_error Collabda::InvalidSource
177
+ end
178
+ end
179
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: collabda
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1a
5
+ platform: ruby
6
+ authors:
7
+ - James Edwards-Jones
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.6'
27
+ description: Collabda uses JSON or YAML files to build a collection of classes.
28
+ email:
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/collabda.rb
34
+ - spec/lib/collabda_spec.rb
35
+ homepage: https://github.com/Jamedjo/Collabda
36
+ licenses:
37
+ - Apache-2.0
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - '>'
51
+ - !ruby/object:Gem::Version
52
+ version: 1.3.1
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.1.9
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: A library for building models from data files
59
+ test_files:
60
+ - spec/lib/collabda_spec.rb