pottery 0.1.0

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