morph 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/CHANGELOG +2 -0
  2. data/README +53 -21
  3. data/lib/morph.rb +50 -44
  4. data/morph.gemspec +6 -6
  5. data/spec/morph_spec.rb +32 -22
  6. metadata +5 -5
data/CHANGELOG CHANGED
@@ -1,3 +1,5 @@
1
+ v0.1.4. can now pass code block to customize the dynamically added methods
2
+
1
3
  v0.1.3. can now set hash of attributes via morph method
2
4
 
3
5
  v0.1.2. fixed problems in packaging of v0.1.1 gem
data/README CHANGED
@@ -1,7 +1,8 @@
1
- Morph allows you to emerge class definitions via calling assignment methods.
2
- Mix with Hpricot for screen scrapping fun.
1
+ Morph allows you to emerge class definitions via calling assignment methods; mix with Hpricot for screen scrapping fun.
3
2
 
4
- == Morph example
3
+
4
+
5
+ == Morph example with Hpricot
5
6
 
6
7
  Here's example code showing Morph playing with Hpricot:
7
8
 
@@ -17,7 +18,7 @@ Here's example code showing Morph playing with Hpricot:
17
18
  label = node.inner_text
18
19
  value = node.next_sibling.inner_text.strip
19
20
 
20
- morph(label, value) # morph magic happening here!
21
+ morph(label, value) # morph magic happening here!
21
22
  end
22
23
  end
23
24
  end
@@ -60,32 +61,63 @@ So, a new company method has appeared:
60
61
  dhh.company #=> "37signals"
61
62
 
62
63
 
64
+
65
+ == Morph example from Hash
66
+
63
67
  How about adding a hash of attribute values?
64
68
 
65
- why.morph :drink => 'tea', :spoons_of_sugar => 2, :milk => 'prefer soya thanks'
69
+ class Order; include Morph; end
70
+ order = Order.new
71
+ order.morph :drink => 'tea', :spoons_of_sugar => 2, :milk => 'prefer soya thanks'
72
+
73
+ order.drink # => "tea"
74
+ order.spoons_of_sugar # => 2
75
+ order.milk # => "prefer soya thanks"
76
+
77
+
78
+
79
+ == Morph example hijacking method creation
80
+
81
+ class VegieFoxes
82
+ include Morph
83
+
84
+ def method_missing symbol, *args
85
+ if (is_writer = symbol.to_s =~ /=\Z/)
86
+ morph_method_missing(symbol, *args) do |base, attribute|
87
+ create_accessors base, attribute
88
+ end
89
+ send(symbol, *args)
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ private
96
+ def create_accessors base, attribute
97
+ base.class_eval "attr_reader :#{attribute}"
66
98
 
67
- why.drink # => "tea"
68
- why.spoons_of_sugar # => 2
69
- why.milk # => "prefer soya thanks"
99
+ base.class_def("#{attribute}=") do |value|
100
+ is_meat = %w[bacon ham].include? value
101
+ value = is_meat ? 'spinach' : value
102
+ instance_variable_set "@#{attribute}".to_sym, value
103
+ end
104
+ end
105
+ end
70
106
 
107
+ foxes = VegieFoxes.new
71
108
 
72
- Time to print the nascent attribute definitions:
109
+ foxes.chunky = 'bacon'
110
+ foxes.inspect # => #<VegieFoxes @chunky="spinach">
73
111
 
74
- puts Hubbit.print_morph_methods
112
+ foxes.chunky = 'cheese'
113
+ puts foxes.inspect # => #<VegieFoxes @chunky="cheese">
75
114
 
76
- attr_accessor :blog
77
- attr_accessor :company
78
- attr_accessor :drink
79
- attr_accessor :email
80
- attr_accessor :location
81
- attr_accessor :milk
82
- attr_accessor :name
83
- attr_accessor :projects
84
- attr_accessor :spoons_of_sugar
85
- => nil
115
+ foxes.pet = 'ham'
116
+ foxes.inspect # => #<VegieFoxes @pet="spinach", @chunky="cheese">
86
117
 
118
+ == Last bits
87
119
 
88
- See examples/ directory for more example code.
120
+ See examples/ directory for some example code.
89
121
  See LICENSE for the terms of this software.
90
122
 
91
123
  . ,
data/lib/morph.rb CHANGED
@@ -1,58 +1,50 @@
1
1
  module Morph
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
 
4
4
  def self.included(base)
5
5
  base.extend ClassMethods
6
6
  base.send(:include, InstanceMethods)
7
+ base.send(:include, MethodMissing)
7
8
  end
8
9
 
9
10
  module ClassMethods
10
11
 
12
+ @@adding_morph_method = false
11
13
  @@morph_methods = {}
12
14
 
13
- def convert_to_morph_method_name label
14
- name = label.downcase.tr('()*',' ').gsub('%','percentage').strip.chomp(':').strip.gsub(/\s/,'_').squeeze('_')
15
- name = '_'+name if name =~ /^\d/
16
- name
15
+ def morph_methods
16
+ @@morph_methods.keys.sort
17
17
  end
18
18
 
19
- def morph_accessor symbol
20
- attribute = symbol.to_s
21
- @@morph_methods[attribute] = true
22
- @@morph_methods[attribute+'='] = true
23
- class_eval "attr_accessor :#{attribute}"
19
+ def adding_morph_method= true_or_false
20
+ @@adding_morph_method = true_or_false
24
21
  end
25
22
 
26
- def morph_methods
27
- @@morph_methods.keys.sort
23
+ def class_def name, &block
24
+ class_eval { define_method name, &block }
28
25
  end
29
26
 
30
- def remove_method symbol
31
- @@morph_methods.delete symbol.to_s
32
- super
33
- end
27
+ protected
34
28
 
35
- def remove_morph_writers
36
- writers = morph_methods.select { |m| m =~ /=\Z/ }
37
- writers.each do |writer|
38
- class_eval "remove_method :#{writer}"
29
+ def method_added symbol
30
+ @@morph_methods[symbol.to_s] = true if @@adding_morph_method
39
31
  end
40
- end
41
32
 
42
- def print_morph_methods
43
- methods = morph_methods
44
- writers = methods.select { |m| m =~ /=\Z/ }
45
- readers = methods.reject { |m| m =~ /=\Z/ }
33
+ def method_removed symbol
34
+ @@morph_methods.delete symbol.to_s if @@morph_methods.has_key? symbol.to_s
35
+ end
46
36
 
47
- accessors = readers.select { |m| writers.include? "#{m}=" }
48
- readers = readers.reject { |m| accessors.include? m }
49
- writers = writers.reject { |m| accessors.include? m.chomp('=') }
37
+ end
50
38
 
51
- attributes = accessors.collect { |attribute| "attr_accessor :#{attribute}\n" }
52
- attributes += readers.collect { |attribute| "attr_reader :#{attribute}\n" }
53
- attributes += writers.collect { |attribute| "attr_writer :#{attribute}\n" }
39
+ module MethodMissing
40
+ def method_missing symbol, *args
41
+ is_writer = symbol.to_s =~ /=\Z/
54
42
 
55
- attributes.join.chop
43
+ if is_writer
44
+ morph_method_missing symbol, *args
45
+ else
46
+ super
47
+ end
56
48
  end
57
49
  end
58
50
 
@@ -82,29 +74,43 @@ module Morph
82
74
  attributes.each { |a, v| morph(a, v) }
83
75
  else
84
76
  label = attributes
85
- attribute = label.is_a?(String) ? self.class.convert_to_morph_method_name(label) : label
77
+ attribute = label.is_a?(String) ? convert_to_morph_method_name(label) : label
86
78
  send("#{attribute}=".to_sym, value)
87
79
  end
88
80
  end
89
81
 
90
- def method_missing symbol, *args
91
- is_writer = symbol.to_s =~ /=\Z/
82
+ protected
92
83
 
93
- if is_writer
84
+ def morph_method_missing symbol, *args
94
85
  attribute = symbol.to_s.chomp '='
86
+
95
87
  if Object.instance_methods.include?(attribute)
96
88
  raise "'#{attribute}' is an instance_method on Object, cannot create accessor methods for '#{attribute}'"
97
- elsif args.size > 0
98
- value = args[0]
99
- empty_value = (value.nil? or (value.is_a?(String) && value.strip.size == 0))
100
- unless empty_value
101
- self.class.morph_accessor attribute.to_sym
89
+ elsif argument_provided? args
90
+ base = self.class
91
+ base.adding_morph_method= true
92
+
93
+ if block_given?
94
+ yield base, attribute
95
+ else
96
+ base.class_eval "attr_accessor :#{attribute}"
102
97
  send(symbol, *args)
103
98
  end
99
+ base.adding_morph_method= false
104
100
  end
105
- else
106
- super
107
101
  end
108
- end
102
+
103
+ private
104
+
105
+ def argument_provided? args
106
+ args.size > 0 && !args[0].nil? && !(args[0].is_a?(String) && args[0].strip.size == 0)
107
+ end
108
+
109
+ def convert_to_morph_method_name label
110
+ name = label.downcase.tr('()*',' ').gsub('%','percentage').strip.chomp(':').strip.gsub(/\s/,'_').squeeze('_')
111
+ name = '_'+name if name =~ /^\d/
112
+ name
113
+ end
114
+
109
115
  end
110
116
  end
data/morph.gemspec CHANGED
@@ -1,17 +1,17 @@
1
1
 
2
- # Gem::Specification for Morph-0.1.3
2
+ # Gem::Specification for Morph-0.1.4
3
3
  # Originally generated by Echoe
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = %q{morph}
7
- s.version = "0.1.3"
7
+ s.version = "0.1.4"
8
8
 
9
9
  s.specification_version = 2 if s.respond_to? :specification_version=
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.authors = ["Rob McKinnon"]
13
- s.date = %q{2008-04-02}
14
- s.description = %q{Morph allows you to emerge class definitions via calling assignment methods.}
13
+ s.date = %q{2008-04-10}
14
+ s.description = %q{Morph allows you to emerge class definitions via calling assignment methods; mix with Hpricot for screen scrapping fun.}
15
15
  s.email = ["rob ~@nospam@~ rubyforge.org"]
16
16
  s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README"]
17
17
  s.files = ["CHANGELOG", "examples/forger.rb", "examples/hubbit.rb", "lib/morph.rb", "LICENSE", "README", "spec/morph_spec.rb", "spec/spec.opts", "Manifest", "morph.gemspec"]
@@ -20,8 +20,8 @@ Gem::Specification.new do |s|
20
20
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Morph", "--main", "README", "--inline-source"]
21
21
  s.require_paths = ["lib"]
22
22
  s.rubyforge_project = %q{morph}
23
- s.rubygems_version = %q{1.0.1}
24
- s.summary = %q{Morph allows you to emerge class definitions via calling assignment methods.}
23
+ s.rubygems_version = %q{1.1.0}
24
+ s.summary = %q{Morph allows you to emerge class definitions via calling assignment methods; mix with Hpricot for screen scrapping fun.}
25
25
  end
26
26
 
27
27
 
data/spec/morph_spec.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/../lib/morph'
2
2
  require File.dirname(__FILE__) + '/morph_spec_helper'
3
3
 
4
-
5
4
  describe Morph, "when writer method that didn't exist before is called with non-nil value" do
6
5
  before :each do
7
6
  remove_morph_methods
@@ -28,6 +27,20 @@ describe Morph, "when writer method that didn't exist before is called with nil
28
27
  it_should_behave_like "class without generated accessor methods added"
29
28
  end
30
29
 
30
+ describe Morph, "when class definition contains methods and morph is included" do
31
+ include MorphSpecHelperMethods
32
+
33
+ after :all do
34
+ remove_morph_methods
35
+ @morphed_class.class_eval "remove_method :happy"
36
+ end
37
+
38
+ it 'should not return methods defined in class in morph_methods list' do
39
+ initialize_morph "class ExampleMorph\n include Morph\n def happy\n 'happy, joy, joy'\n end\n end"
40
+ morph_methods.should be_empty
41
+ end
42
+ end
43
+
31
44
  describe Morph, "when writer method that didn't exist before is called with blank space attribute value" do
32
45
  before :each do
33
46
  remove_morph_methods
@@ -38,7 +51,6 @@ describe Morph, "when writer method that didn't exist before is called with blan
38
51
  it_should_behave_like "class without generated accessor methods added"
39
52
  end
40
53
 
41
-
42
54
  describe Morph, 'when morph method used to set attribute value' do
43
55
 
44
56
  before :each do
@@ -168,36 +180,34 @@ describe Morph, "when class= is called" do
168
180
  end
169
181
  end
170
182
 
171
-
172
- describe Morph, 'when remove_morph_writers is called after a generated method has been added' do
183
+ describe Morph, 'when passing block to morph_method_missing' do
173
184
 
174
185
  include MorphSpecHelperMethods
175
186
  before :all do initialize_morph; end
176
- after :all do remove_morph_methods; end
177
-
178
- before :each do
179
- remove_morph_methods
180
- @morph.noise= 'quack'
181
- @attribute = 'noise'
182
- @morphed_class.remove_morph_writers
183
- end
187
+ after :each do remove_morph_methods; end
184
188
 
185
- it 'should remove a morph generated writer method from morph_methods list' do
186
- morph_methods.include?('noise=').should == false
187
- morph_methods.size.should == 1
189
+ it 'should class_eval the block' do
190
+ @morph.morph_method_missing(:chunky, 'bacon') do |base, attribute|
191
+ base.class_eval "def #{attribute}; 'spinach'; end"
192
+ end
193
+ @morph.respond_to?(:chunky).should == true
194
+ @morph.chunky.should == 'spinach'
195
+ @morphed_class.class_eval "remove_method :chunky"
196
+ lambda { @morph.chunky }.should raise_error
188
197
  end
189
198
 
190
- it 'should remove a morph generated writer method from class instance_methods list' do
191
- instance_methods.include?('noise=').should == false
192
- end
193
-
194
- it 'should be able to print morph method declarations' do
195
- @morphed_class.print_morph_methods.should == %Q|attr_reader :#{@attribute}|
199
+ it 'should class_eval the block' do
200
+ @morph.morph_method_missing :chunky, 'bacon' do |base, attribute|
201
+ base.class_def(attribute) { 'spinach' }
202
+ end
203
+ @morph.respond_to?(:chunky).should == true
204
+ @morph.chunky.should == 'spinach'
205
+ @morphed_class.class_eval "remove_method :chunky"
206
+ lambda { @morph.chunky }.should raise_error
196
207
  end
197
208
 
198
209
  end
199
210
 
200
-
201
211
  describe Morph, "when converting label text to morph method name" do
202
212
 
203
213
  include MorphSpecHelperMethods
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: morph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob McKinnon
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-04-02 00:00:00 +01:00
12
+ date: 2008-04-10 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: Morph allows you to emerge class definitions via calling assignment methods.
16
+ description: Morph allows you to emerge class definitions via calling assignment methods; mix with Hpricot for screen scrapping fun.
17
17
  email:
18
18
  - rob ~@nospam@~ rubyforge.org
19
19
  executables: []
@@ -63,9 +63,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
63
  requirements: []
64
64
 
65
65
  rubyforge_project: morph
66
- rubygems_version: 1.0.1
66
+ rubygems_version: 1.1.0
67
67
  signing_key:
68
68
  specification_version: 2
69
- summary: Morph allows you to emerge class definitions via calling assignment methods.
69
+ summary: Morph allows you to emerge class definitions via calling assignment methods; mix with Hpricot for screen scrapping fun.
70
70
  test_files: []
71
71