pottery 0.1.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/CHANGELOG ADDED
@@ -0,0 +1 @@
1
+ v0.1.0. initial release
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright 2008 Rob McKinnon
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,8 @@
1
+ CHANGELOG
2
+ lib/pottery.rb
3
+ LICENSE
4
+ README
5
+ spec/pottery_spec.rb
6
+ spec/pottery_spec_helper.rb
7
+ spec/spec.opts
8
+ Manifest
data/README ADDED
@@ -0,0 +1,61 @@
1
+ Pottery allows you to emerge class definitions via calling assignment methods and
2
+ persist instances to a database; requires Morph and Soup gems.
3
+
4
+ [Note API subject to change]
5
+
6
+ == Pottery example
7
+
8
+ Here's example code showing Pottery playing with Hpricot:
9
+
10
+ > sqlite3 soup.db
11
+
12
+ > irb
13
+
14
+ require 'pottery'
15
+ require 'hpricot'; require 'open-uri'
16
+
17
+ class Hubbit
18
+ include Pottery
19
+
20
+ def initialize name=nil
21
+ if name
22
+ doc = Hpricot open("http://github.com/#{name}")
23
+
24
+ (doc/'label').collect do |node|
25
+ label = node.inner_text
26
+ value = node.next_sibling.inner_text.strip
27
+
28
+ morph(label, value)
29
+ end
30
+ morph(:id_name, name)
31
+ end
32
+ end
33
+ end
34
+
35
+
36
+ The model emerges from the data. Let's start by looking up 'why':
37
+
38
+ why = Hubbit.new 'why'
39
+
40
+ What new methods do we have?
41
+
42
+ Hubbit.morph_methods # => ["email", "email=", "name", "name="]
43
+
44
+ Ah-ha, so we have a name attribute now:
45
+
46
+ why.name #=> "why the lucky stiff"
47
+
48
+
49
+ Let's save why for later
50
+
51
+ why.save
52
+
53
+
54
+ Ok, now it's later, let's restore why from the database
55
+
56
+ why = Hubbit.restore('why') #=> <Hubbit @id_name="why", @name="why the lucky stiff", @email="why@why...">
57
+
58
+
59
+
60
+ See LICENSE for the terms of this software.
61
+
data/lib/pottery.rb ADDED
@@ -0,0 +1,101 @@
1
+ require 'morph'
2
+ require 'soup'
3
+
4
+ module Pottery
5
+ VERSION = "0.1.0"
6
+
7
+ def self.included(base)
8
+ Soup.prepare
9
+ base.extend Pottery::ClassMethods, Morph::ClassMethods
10
+ base.send(:include, Morph::InstanceMethods)
11
+ base.send(:include, Morph::MethodMissing)
12
+ base.send(:include, InstanceMethods)
13
+ end
14
+
15
+ class PotterySnip < Snip
16
+ def set_value(name, value); super; end
17
+ end
18
+
19
+ module ClassMethods
20
+ def restore name
21
+ snip = Pottery::PotterySnip[name]
22
+ if snip
23
+ instance = self.new
24
+ attributes = snip.attributes
25
+ unless attributes.empty?
26
+ id_name = attributes.delete('name')
27
+ name = attributes.delete('name_name')
28
+ attributes['id_name'] = id_name if id_name
29
+ attributes['name'] = name if name
30
+ instance.morph attributes
31
+ end
32
+ instance
33
+ else
34
+ nil
35
+ end
36
+ end
37
+ end
38
+
39
+ module InstanceMethods
40
+
41
+ def save
42
+ if respond_to?('id_name') && !id_name.nil? && !(id_name.to_s.strip.size == 0)
43
+ snip = Pottery::PotterySnip.new
44
+ morph_attributes.each_pair do |symbol, value|
45
+ symbol = convert_if_name_or_id_name(symbol)
46
+ snip.set_value(symbol.to_s, value)
47
+ end
48
+ snip.save
49
+ self
50
+ else
51
+ raise 'unique id_name must be defined'
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def convert_if_name_or_id_name(symbol)
58
+ case symbol
59
+ when :id_name
60
+ :name
61
+ when :name
62
+ :name_name
63
+ else
64
+ symbol
65
+ end
66
+ end
67
+ =begin
68
+ def method_missing symbol, *args
69
+ is_writer = symbol.to_s =~ /=\Z/
70
+ if is_writer
71
+ morph_method_missing symbol, *args do |base, attribute|
72
+ # alternative 1:
73
+ # base.class_eval "def #{attribute}; @snip.get_value('#{attribute}') ; end"
74
+ # base.class_eval "def #{attribute}= value; @snip ||= Pottery::PotterySnip.new; @snip.set_value('#{attribute}', value) ; end"
75
+
76
+ # alternative 2:
77
+ # base.class_eval { define_method(attribute) { @snip.get_value(attribute) } }
78
+ # base.class_eval do
79
+ # define_method("#{attribute}=") do |value|
80
+ # @snip ||= Pottery::PotterySnip.new
81
+ # @snip.set_value(attribute, value)
82
+ # end
83
+ # end
84
+
85
+ # alternative 3:
86
+ base.class_def(attribute) do
87
+ @snip.get_value(attribute)
88
+ end
89
+ base.class_def("#{attribute}=") do |value|
90
+ @snip ||= Pottery::PotterySnip.new
91
+ @snip.set_value(attribute, value)
92
+ end
93
+ end
94
+ send(symbol, *args)
95
+ else
96
+ super
97
+ end
98
+ end
99
+ =end
100
+ end
101
+ end
data/pottery.gemspec ADDED
@@ -0,0 +1,57 @@
1
+
2
+ # Gem::Specification for Pottery-0.1.0
3
+ # Originally generated by Echoe
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{pottery}
7
+ s.version = "0.1.0"
8
+
9
+ s.specification_version = 2 if s.respond_to? :specification_version=
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.authors = ["Rob McKinnon"]
13
+ s.date = %q{2008-04-14}
14
+ s.description = %q{Pottery allows you to emerge class definitions via calling assignment methods and}
15
+ s.email = ["rob ~@nospam@~ rubyforge.org"]
16
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README"]
17
+ s.files = ["CHANGELOG", "lib/pottery.rb", "LICENSE", "README", "spec/pottery_spec.rb", "spec/pottery_spec_helper.rb", "spec/spec.opts", "Manifest", "pottery.gemspec"]
18
+ s.has_rdoc = true
19
+ s.homepage = %q{}
20
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Pottery", "--main", "README", "--inline-source"]
21
+ s.require_paths = ["lib"]
22
+ s.rubyforge_project = %q{pottery}
23
+ s.rubygems_version = %q{1.1.0}
24
+ s.summary = %q{Pottery allows you to emerge class definitions via calling assignment methods and}
25
+
26
+ s.add_dependency(%q<soup>, ["= 0.1.5"])
27
+ s.add_dependency(%q<morph>, [">= 0.1.5"])
28
+ end
29
+
30
+
31
+ # # Original Rakefile source (requires the Echoe gem):
32
+ #
33
+ # require 'rubygems'
34
+ # require 'spec'
35
+ # require 'lib/pottery'
36
+ #
37
+ # begin
38
+ # require 'echoe'
39
+ #
40
+ # Echoe.new("pottery", Pottery::VERSION) do |m|
41
+ # m.author = ["Rob McKinnon"]
42
+ # m.email = ["rob ~@nospam@~ rubyforge.org"]
43
+ # m.description = File.readlines("README").first
44
+ # m.rubyforge_name = "pottery"
45
+ # m.rdoc_options << '--inline-source'
46
+ # m.rdoc_pattern = ["README", "CHANGELOG", "LICENSE"]
47
+ # m.dependencies = ["soup =0.1.5", "morph >=0.1.5"]
48
+ # end
49
+ #
50
+ # rescue LoadError
51
+ # puts "You need to install the echoe gem to perform meta operations on this gem"
52
+ # end
53
+ #
54
+ # desc "Open an irb session preloaded with this library"
55
+ # task :console do
56
+ # sh "irb -rubygems -r ./lib/pottery.rb"
57
+ # end
@@ -0,0 +1,145 @@
1
+ require File.dirname(__FILE__) + '/../lib/pottery'
2
+ require File.dirname(__FILE__) + '/pottery_spec_helper'
3
+
4
+ describe Pottery, "when writer method that didn't exist before is called with non-nil value" do
5
+ include PotterySpecHelperMethods
6
+
7
+ before :each do
8
+ remove_morph_methods
9
+ @quack = 'quack'
10
+ @pottery.noise= @quack
11
+ @attribute = 'noise'
12
+ @expected_morph_methods_count = 2
13
+ end
14
+
15
+ it_should_behave_like "class with generated accessor methods added"
16
+
17
+ it 'should return assigned value when reader method called' do
18
+ @pottery.noise.should == @quack
19
+ end
20
+ end
21
+
22
+ describe Pottery, "when pottery class is loaded" do
23
+ include PotterySpecHelperMethods
24
+
25
+ it 'should call Soup.prepare to setup database' do
26
+ Soup.should_receive(:prepare)
27
+ initialize_pottery_class
28
+ end
29
+
30
+ it 'should add restore class method' do
31
+ initialize_pottery_class
32
+ @potted_class.respond_to?('restore').should be_true
33
+ end
34
+ end
35
+
36
+ describe Pottery, "when reader method that doesn't exist is called" do
37
+ include PotterySpecHelperMethods
38
+
39
+ it 'should set attribute and value on the Snip instance' do
40
+ initialize_pottery_and_snip
41
+ lambda { @pottery.noise }.should raise_error(/undefined method `noise'/)
42
+ end
43
+ end
44
+
45
+
46
+ describe Pottery, "when save method is called after id_name has been set" do
47
+ include PotterySpecHelperMethods
48
+
49
+ after :each do remove_morph_methods; end
50
+
51
+ it 'should call save on the Snip instance' do
52
+ initialize_pottery_and_snip
53
+ @pottery.id_name = 'red'
54
+ @snip.should_receive(:save)
55
+ @pottery.save
56
+ end
57
+
58
+ it 'should call set_value on the Snip instance with attribute and value' do
59
+ initialize_pottery_and_snip
60
+ @pottery.id_name = 'red'
61
+
62
+ @snip.stub!(:save)
63
+ @snip.should_receive(:set_value).with('name', 'red')
64
+ @pottery.save
65
+ end
66
+ end
67
+
68
+
69
+ describe Pottery, "when save method is called after name and id_name has been set" do
70
+ include PotterySpecHelperMethods
71
+
72
+ after :each do remove_morph_methods; end
73
+
74
+ it 'should call save on the Snip instance' do
75
+ initialize_pottery_and_snip
76
+ @pottery.id_name = 'red'
77
+ @pottery.name = 'blue'
78
+ @snip.should_receive(:save)
79
+ @pottery.save
80
+ end
81
+
82
+ it 'should call set_value on the Snip instance with attribute and value' do
83
+ initialize_pottery_and_snip
84
+ @pottery.id_name = 'red'
85
+ @pottery.name = 'blue'
86
+
87
+ @snip.stub!(:save)
88
+ @snip.should_receive(:set_value).with('name', 'red')
89
+ @snip.should_receive(:set_value).with('name_name', 'blue')
90
+ @pottery.save
91
+ end
92
+ end
93
+
94
+
95
+ describe Pottery, "when save method is called after id_name has not been set" do
96
+ include PotterySpecHelperMethods
97
+
98
+ after :all do remove_morph_methods; end
99
+
100
+ it 'should call save on the Snip instance' do
101
+ initialize_pottery_and_snip
102
+ @pottery.colour = 'red'
103
+ lambda { @pottery.save }.should raise_error(/unique id_name must be defined/)
104
+ end
105
+ end
106
+
107
+ describe Pottery, "when restore method is called with name on class" do
108
+ include PotterySpecHelperMethods
109
+
110
+ before :each do
111
+ initialize_pottery_and_snip
112
+ @snip = mock(Pottery::PotterySnip)
113
+ @snip.stub!(:attributes).and_return({})
114
+ Pottery::PotterySnip.stub!('[]'.to_sym).and_return @snip
115
+ end
116
+
117
+ it 'should call Snip [] load method with given name' do
118
+ name = 'red'
119
+ Pottery::PotterySnip.should_receive('[]'.to_sym).with(name).and_return nil
120
+ @potted_class.restore(name)
121
+ end
122
+
123
+ it 'should return an instance of the class if Snip [] returns not nil' do
124
+ @potted_class.restore('red').should be_an_instance_of(@potted_class)
125
+ end
126
+
127
+ it 'should return nil if Snip [] returns nil' do
128
+ Pottery::PotterySnip.stub!('[]'.to_sym).and_return nil
129
+ @potted_class.restore('red').should be_nil
130
+ end
131
+
132
+ it 'should set attributes from snip on to class instance and return instance' do
133
+ attributes = {:name => 'red', :state => 'blue'}
134
+
135
+ @snip.stub!(:attributes).and_return(attributes)
136
+ Pottery::PotterySnip.stub!('[]'.to_sym).and_return @snip
137
+ instance = mock(@potted_class)
138
+ @potted_class.stub!(:new).and_return instance
139
+
140
+ instance.should_receive(:morph).with(attributes)
141
+ @potted_class.restore('red').should == instance
142
+ end
143
+
144
+ end
145
+
@@ -0,0 +1,108 @@
1
+ require File.dirname(__FILE__) + '/../lib/pottery'
2
+
3
+ module PotterySpecHelperMethods
4
+
5
+ def initialize_pottery_class
6
+ @potted_class = eval 'class ExamplePottery; include Pottery; end'
7
+ end
8
+
9
+ def initialize_pottery
10
+ Soup.stub!(:prepare)
11
+ initialize_pottery_class
12
+ @original_instance_methods = @potted_class.instance_methods
13
+ @pottery = @potted_class.new
14
+ end
15
+
16
+ def initialize_snip
17
+ @snip = mock(Snip)
18
+ @snip.stub!(:set_value)
19
+ Pottery::PotterySnip.stub!(:new).and_return @snip
20
+ end
21
+
22
+ def initialize_pottery_and_snip
23
+ initialize_pottery
24
+ initialize_snip
25
+ end
26
+
27
+ def remove_morph_methods
28
+ @potted_class.instance_methods.each do |method|
29
+ @potted_class.class_eval "remove_method :#{method}" unless @original_instance_methods.include?(method)
30
+ end
31
+ end
32
+
33
+ def instance_methods
34
+ @potted_class.instance_methods
35
+ end
36
+
37
+ def morph_methods
38
+ @potted_class.morph_methods
39
+ end
40
+
41
+ def check_convert_to_pottery_method_name label, method_name
42
+ initialize_pottery_class
43
+ @potted_class.convert_to_pottery_method_name(label).should == method_name
44
+ end
45
+
46
+ def each_attribute
47
+ if @attribute
48
+ yield @attribute
49
+ elsif @attributes
50
+ @attributes.each {|a| yield a }
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "class with generated accessor methods added", :shared => true do
56
+ include PotterySpecHelperMethods
57
+
58
+ before :all do initialize_pottery; end
59
+ after :all do remove_morph_methods; end
60
+
61
+ it 'should add reader method to class instance_methods list' do
62
+ each_attribute { |a| instance_methods.should include(a.to_s) }
63
+ end
64
+
65
+ it 'should add writer method to class instance_methods list' do
66
+ each_attribute { |a| instance_methods.should include("#{a}=") }
67
+ end
68
+
69
+ it 'should add reader method to class morph_methods list' do
70
+ each_attribute { |a| morph_methods.should include(a.to_s) }
71
+ end
72
+
73
+ it 'should add writer method to class morph_methods list' do
74
+ each_attribute { |a| morph_methods.should include("#{a}=") }
75
+ end
76
+
77
+ it 'should only have generated accessor methods in morph_methods list' do
78
+ morph_methods.size.should == @expected_morph_methods_count
79
+ end
80
+
81
+ end
82
+
83
+ describe "class without generated accessor methods added", :shared => true do
84
+ include PotterySpecHelperMethods
85
+
86
+ before :all do initialize_pottery; end
87
+ after :all do remove_morph_methods; end
88
+
89
+ it 'should not add reader method to class instance_methods list' do
90
+ instance_methods.should_not include(@attribute)
91
+ end
92
+
93
+ it 'should not add writer method to class instance_methods list' do
94
+ instance_methods.should_not include("#{@attribute}=")
95
+ end
96
+
97
+ it 'should not add reader method to class morph_methods list' do
98
+ morph_methods.should_not include(@attribute)
99
+ end
100
+
101
+ it 'should not add writer method to class morph_methods list' do
102
+ morph_methods.should_not include("#{@attribute}=")
103
+ end
104
+
105
+ it 'should have empty morph_methods list' do
106
+ morph_methods.size.should == 0
107
+ end
108
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,7 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
7
+ --backtrace
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pottery
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rob McKinnon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-04-14 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: soup
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - "="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.1.5
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: morph
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.1.5
32
+ version:
33
+ description: Pottery allows you to emerge class definitions via calling assignment methods and
34
+ email:
35
+ - rob ~@nospam@~ rubyforge.org
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - CHANGELOG
42
+ - LICENSE
43
+ - README
44
+ files:
45
+ - CHANGELOG
46
+ - lib/pottery.rb
47
+ - LICENSE
48
+ - README
49
+ - spec/pottery_spec.rb
50
+ - spec/pottery_spec_helper.rb
51
+ - spec/spec.opts
52
+ - Manifest
53
+ - pottery.gemspec
54
+ has_rdoc: true
55
+ homepage: ""
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --line-numbers
59
+ - --inline-source
60
+ - --title
61
+ - Pottery
62
+ - --main
63
+ - README
64
+ - --inline-source
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project: pottery
82
+ rubygems_version: 1.1.0
83
+ signing_key:
84
+ specification_version: 2
85
+ summary: Pottery allows you to emerge class definitions via calling assignment methods and
86
+ test_files: []
87
+