morph 0.1.3 → 0.1.4

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.
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