king_placeholder 0.0.3 → 1.0.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/README.rdoc CHANGED
@@ -1,10 +1,11 @@
1
1
  = KingPlaceholder
2
2
  {<img src="https://secure.travis-ci.org/salesking/king_placeholder.png?branch=master" alt="Build Status" />}[http://travis-ci.org/salesking/king_placeholder]
3
+
3
4
  This gem was extracted from SalesKing, where it does placeholders substitution
4
5
  for user supplied strings in Email-, Text- and Export-Templates.
5
6
 
6
7
  Placeholders are declared in each class and afterwards any strings containing
7
- [placeholders] can be parsed in the scope of the model.
8
+ <b>[placeholders]</b> can be parsed in the scope of the model.
8
9
 
9
10
  Parsing is done by a simple statemachine using regex for placeholder detection.
10
11
 
@@ -16,7 +17,7 @@ Define the available methods in your class with 'has_placeholders'
16
17
  include KingPlaceholder
17
18
  has_many :comments
18
19
  has_one :company
19
- has_placeholders :firstname, :email
20
+ has_placeholders :name, :email
20
21
  end
21
22
 
22
23
  class Comment
@@ -43,6 +44,33 @@ Handle relations
43
44
  @user.expand_placeholders("[company.name]")
44
45
  => MyCompany
45
46
 
47
+ Set a custom formatting method(in format_placeholder) throught which all fields
48
+ are run, if you have special money or date formatters.
49
+
50
+ class User
51
+ include KingPlaceholder
52
+ has_placeholders :created_at
53
+
54
+ def format_placeholder(field)
55
+ I18n.l( self.send(field) )
56
+ end
57
+ end
58
+
59
+ Use callbacks to setup / teardown env variables
60
+
61
+ class User
62
+ include KingPlaceholder
63
+ has_placeholders :name, :email
64
+
65
+ def before_expand_placeholders
66
+ I18n.locale = self.language
67
+ end
68
+ def after_expand_placeholders
69
+ I18n.locale = nil
70
+ end
71
+ end
72
+
73
+
46
74
  Also see specs
47
75
 
48
76
  == TODO
@@ -18,9 +18,6 @@ Gem::Specification.new do |gem|
18
18
  gem.add_runtime_dependency 'statemachine'
19
19
  gem.add_runtime_dependency 'i18n'
20
20
  gem.add_runtime_dependency 'activesupport'
21
- gem.add_runtime_dependency 'actionpack'
22
- #gem.add_runtime_dependency 'actionview'
23
- #gem.add_runtime_dependency 'king_views'
24
21
 
25
22
  gem.add_development_dependency 'activerecord'
26
23
  gem.add_development_dependency 'rdoc'
@@ -1,8 +1,5 @@
1
1
  require 'statemachine'
2
- require 'action_view' # king_views related suxs
3
- require 'action_controller' # king_views
4
- require 'king_views'
5
-
2
+ require 'active_support/core_ext/string/inflections'
6
3
  module KingPlaceholder
7
4
  # Statemachine for placeholder substitution
8
5
  # The statemachine is created and its state updated from within the Context
@@ -31,7 +28,6 @@ module KingPlaceholder
31
28
  # machine.sm.match
32
29
  # machine.result
33
30
  class Parser
34
- include ::KingFormat::FormattingHelper
35
31
  # reference to statemachine
36
32
  attr_accessor :sm
37
33
  # incoming string
@@ -53,13 +49,9 @@ module KingPlaceholder
53
49
  @opts = opts
54
50
  end
55
51
 
56
- # Before matching is done this method set's up environment variables like
57
- # current_language => i18n.locale
58
- # TODO: decouple and outsource into before_method block to set env vars from outside
52
+ # Before matching is done deep copy content, because otherwise result.gsub!
53
+ # breaks recursion
59
54
  def prepare
60
- init_locale #I18n.locale
61
- set_format_opts # merge date/money format into thread var
62
- # deep copy content, because otherwise result.gsub! breaks recursion
63
55
  @result = @content.dup
64
56
  end
65
57
 
@@ -87,10 +79,8 @@ module KingPlaceholder
87
79
  @sm.finished_matching
88
80
  end
89
81
 
90
- # When finished this method is called to cleanup f.ex. environment variables
91
- # like I18n.locale
92
82
  def cleanup
93
- @current_language && @current_language.reset_locale
83
+
94
84
  end
95
85
 
96
86
  private
@@ -99,7 +89,12 @@ module KingPlaceholder
99
89
  # namespace e.g. [price_to_pay]
100
90
  def sub_string
101
91
  return unless obj.is_placeholder?(@field)
102
- value = strfval(obj, @field)
92
+ value = if @opts[:formatter]
93
+ obj.send(@opts[:formatter], @field)
94
+ else
95
+ obj.send @field
96
+ end
97
+ #value = strfval(obj, @field)
103
98
  @result.gsub!(@placeholder, value.to_s) if value
104
99
  end
105
100
 
@@ -162,44 +157,5 @@ module KingPlaceholder
162
157
  end
163
158
  end
164
159
 
165
- # set format options for KingFormat date/money helpers
166
- # => strfval + strfmoney
167
- # very important, without those all formatting in views screws up
168
- def set_format_opts
169
- Thread.current[:default_currency_format] = if defined?(::Company) && ::Company.current
170
- ::Company.current.money_format
171
- elsif obj.respond_to?(:company) && obj.company
172
- obj.company.money_format
173
- elsif defined?(::Company) && obj.is_a?(::Company)
174
- obj.money_format
175
- else
176
- nil
177
- end
178
-
179
- Thread.current[:default_date_format] = if defined?(::Company) && ::Company.current
180
- ::Company.current.date_format
181
- elsif obj.respond_to?(:company) && obj.company
182
- obj.company.date_format
183
- elsif defined?(::Company) && obj.is_a?(::Company)
184
- obj.date_format
185
- else
186
- nil
187
- end
188
- end
189
-
190
- # Set i18n.locale to given or present custom company locale
191
- # locale is memoized in instance @current_language.
192
- # Only set if a company and the language is available
193
- # === Parameter
194
- # locale<String>:: locale code en, de,..
195
- def init_locale(locale=nil)
196
- if (locale || (obj.respond_to?(:language) && obj.language)) \
197
- && obj.respond_to?(:company) && obj.company
198
- @current_language ||= begin
199
- # find lang in scope of company
200
- ::Language.init_locale(obj.company, locale || obj.language)
201
- end
202
- end
203
- end
204
160
  end #class
205
161
  end #module
@@ -1,3 +1,3 @@
1
1
  module KingPlaceholder
2
- VERSION = "0.0.3"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -21,15 +21,33 @@ module KingPlaceholder
21
21
  end
22
22
 
23
23
  module ClassMethods
24
- # Define fields(methods) available to placeholder substitution
24
+ # Define which fields(methods) are available to placeholder substitution.
25
+ #
26
+ # `before/after_expand_placeholders` hooks are run before the statemachine
27
+ # parsing. Define those methods to setup env variables like I18n.locale or
28
+ # whatever is required to format output strings.
29
+ # The block gets the current parsed object as method argument
30
+ # @example
31
+ # def before_expand_placeholders
32
+ # I18n.locale = self.language
33
+ # end
34
+ # def after_expand_placeholders
35
+ # I18n.locale = nil
36
+ # end
37
+ #
25
38
  # @param [Array[<Symbol>] fieldnames
26
39
  def has_placeholders(*fieldnames)
27
40
  self.placeholders = fieldnames
41
+ include ActiveSupport::Callbacks
42
+ define_callbacks :expand_placeholders
43
+ set_callback :expand_placeholders, :before, :before_parsing
44
+ set_callback :expand_placeholders, :after, :after_parsing
28
45
  include InstanceMethods
29
46
  end
30
47
  end
31
48
 
32
49
  module InstanceMethods
50
+
33
51
  # Check if a given field is declared as placeholder
34
52
  # @param [Object] fieldname to search in placeholders array
35
53
  # @return [Boolean]true if available
@@ -87,20 +105,31 @@ module KingPlaceholder
87
105
  # @param [Object] opts - unused for now
88
106
  # @return [Hash|Array|String] whatever type you throw in is also returned
89
107
  def expand_placeholders(content, opts={})
90
- if content.is_a?(Array) # Expand all array elements and go recursive
108
+ if content.is_a?(Array)
91
109
  result = []
92
110
  content.each{|element| result << self.expand_placeholders(element, opts) }
93
111
  return result
94
- elsif content.is_a?(Hash) # Expand all hash elements and go recursive
112
+ elsif content.is_a?(Hash)
95
113
  result = {}
96
114
  content.each_pair{ |key,val| result[key] = self.expand_placeholders(val, opts) }
97
115
  return result
98
116
  else # Only proceed with strings
99
117
  return content unless content.is_a?(String)
100
118
  end
101
- parser = KingPlaceholder::Parser.new(self, content, opts)
102
- parser.sm.match
103
- parser.result if parser.sm.state == :finished
119
+ run_callbacks :expand_placeholders do
120
+ opts[:formatter] = :format_placeholder if self.respond_to?(:format_placeholder)
121
+ parser = KingPlaceholder::Parser.new(self, content, opts)
122
+ parser.sm.match
123
+ parser.result if parser.sm.state == :finished
124
+ end
104
125
  end
126
+
127
+ protected
128
+ def before_parsing
129
+ before_expand_placeholders if self.respond_to?(:before_expand_placeholders)
130
+ end
131
+ def after_parsing
132
+ after_expand_placeholders if self.respond_to?(:after_expand_placeholders)
133
+ end
105
134
  end # instance methods
106
135
  end # KingPlaceholders
@@ -18,39 +18,47 @@ end
18
18
 
19
19
  class Detail
20
20
  include KingPlaceholder
21
- include KingFormat::MoneyFields
22
21
  attr_accessor :int_field, :money_field, :secret_field, :currency
23
22
  attr_accessor :master
24
- has_money_fields :money_field
25
23
  has_placeholders :int_field, :money_field
26
24
  end
27
25
 
28
26
  describe 'Class with placeholders' do
29
27
 
30
28
  before :each do
31
- I18n.locale = :en_master
32
- # Thread.current[:default_currency_format] = I18n.t(:'number.currency.format')
33
29
  @record = Detail.new
34
- @record.int_field = 1000
35
- @record.money_field = 12.34
30
+ @record.int_field = 1002
31
+ @record.money_field = 12.333
36
32
  end
37
33
 
38
34
  it 'should have native values' do
39
- @record.int_field.should == 1000
40
- @record.money_field.should == 12.34
35
+ @record.int_field.should == 1002
36
+ @record.money_field.should == 12.333
41
37
  end
42
38
 
43
- it 'should have placeholder values' do
44
- @record.expand_placeholders('[int_field]').should == '1000'
45
- @record.expand_placeholders('[money_field]').should == '$12.34'
39
+ it 'should have placeholder value' do
40
+ @record.expand_placeholders('[int_field]').should == '1002'
41
+ @record.expand_placeholders('[money_field]').should == '12.333'
46
42
  end
47
-
43
+ it 'should expand placeholders in an array' do
44
+ @record.expand_placeholders(['[int_field]', '[money_field]', 'static']).should == ['1002', '12.333', 'static']
45
+ end
46
+
47
+ it 'should expand placeholders in a hash' do
48
+ @record.expand_placeholders( :key1 => '[int_field]',
49
+ :key2 => '[money_field]',
50
+ :key3 => 'static'
51
+ ).should ==
52
+ { :key1 => '1002',
53
+ :key2 => '12.333',
54
+ :key3 => 'static' }
55
+ end
48
56
  end
49
57
 
50
- describe 'Expanding of strings containing placeholder' do
58
+ describe 'Placeholder substitution' do
51
59
 
52
60
  before :each do
53
- I18n.locale = :en_master
61
+ I18n.locale = :en
54
62
  @side = Side.new
55
63
  @side.field = 123
56
64
  @master = Master.new
@@ -72,100 +80,94 @@ describe 'Expanding of strings containing placeholder' do
72
80
  @master.details = [@detail1, @detail2]
73
81
  end
74
82
 
75
- it 'should expand placeholders with no placeholders there' do
76
- @detail1.expand_placeholders('without placeholder').should == 'without placeholder'
77
- @detail1.expand_placeholders('[]').should == '[]'
78
- @detail1.expand_placeholders('').should == ''
79
- @detail1.expand_placeholders(nil).should == nil
80
- @detail1.expand_placeholders("\n").should == "\n"
81
- end
82
-
83
- it 'should expand placeholders with simple fieldname' do
84
- @detail1.expand_placeholders('[int_field]').should == '1001'
85
- @detail1.expand_placeholders("[int_field]\n").should == "1001\n"
86
- @detail1.expand_placeholders('[int_field]---[int_field]').should == '1001---1001'
87
- @detail1.expand_placeholders('[int_field]---[money_field]').should == '1001---$12.34'
88
- end
83
+ context 'with direct lookup' do
89
84
 
90
- it 'should not expand placeholder for secret field' do
91
- @detail1.expand_placeholders('[secret_field]').should == 'UNKNOWN for Detail: secret_field'
92
- end
85
+ it 'should return without placeholders' do
86
+ @detail1.expand_placeholders('without placeholder').should == 'without placeholder'
87
+ @detail1.expand_placeholders('[]').should == '[]'
88
+ @detail1.expand_placeholders('').should == ''
89
+ @detail1.expand_placeholders(nil).should == nil
90
+ @detail1.expand_placeholders("\n").should == "\n"
91
+ end
93
92
 
94
- it 'should expand placeholder with namespaced fieldname' do
95
- @detail1.expand_placeholders('[master.string_field]').should == 'foo'
96
- @detail1.expand_placeholders('[master.string_field] [int_field]').should == 'foo 1001'
97
- end
93
+ it 'should expand with simple fieldname' do
94
+ @detail1.expand_placeholders('[int_field]').should == '1001'
95
+ @detail1.expand_placeholders("[int_field]\n").should == "1001\n"
96
+ @detail1.expand_placeholders('[int_field]---[int_field]--[money_field]').should == '1001---1001--12.34'
97
+ end
98
98
 
99
- it 'should expand placeholder with namespaced to self fieldname' do
100
- @detail1.expand_placeholders('[detail.int_field]').should == '1001'
101
- @detail1.expand_placeholders('[detail.master.string_field] [detail.int_field]').should == 'foo 1001'
102
- end
99
+ it 'should not parse unknown field' do
100
+ @detail1.expand_placeholders('[secret_field]').should == 'UNKNOWN for Detail: secret_field'
101
+ end
103
102
 
104
- it 'should expand with multiple steps' do
105
- @detail1.expand_placeholders('[master.side.field]').should == '123'
106
- end
103
+ it 'should expand placeholder with not existing namespaces' do
104
+ @detail1.expand_placeholders('[nothing.string_field]').should == 'UNKNOWN for Detail: nothing.string_field'
105
+ end
107
106
 
108
- it 'should expand placeholder with not existing namespaces' do
109
- @detail1.expand_placeholders('[nothing.string_field]').should == 'UNKNOWN for Detail: nothing.string_field'
107
+ it 'should expand placeholder with wrong namespaces' do
108
+ @detail1.expand_placeholders('[master.this.are.too.much.namespaces]').should == 'UNKNOWN for Master: this.are.too.much.namespaces'
109
+ @detail1.expand_placeholders('[this.are.too.much.namespaces]').should == 'UNKNOWN for Detail: this.are.too.much.namespaces'
110
+ @detail1.expand_placeholders('[...]').should == 'UNKNOWN for Detail: ...'
111
+ @detail1.expand_placeholders('[unknown]').should == 'UNKNOWN for Detail: unknown'
112
+ end
110
113
  end
111
114
 
112
- it 'should expand placeholder with wrong namespaces' do
113
- @detail1.expand_placeholders('[master.this.are.too.much.namespaces]').should == 'UNKNOWN for Master: this.are.too.much.namespaces'
114
- @detail1.expand_placeholders('[this.are.too.much.namespaces]').should == 'UNKNOWN for Detail: this.are.too.much.namespaces'
115
- @detail1.expand_placeholders('[...]').should == 'UNKNOWN for Detail: ...'
116
- @detail1.expand_placeholders('[unknown]').should == 'UNKNOWN for Detail: unknown'
117
- end
115
+ context 'with namespace' do
118
116
 
119
- it 'should expand placeholder group' do
120
- @master.expand_placeholders('[details][int_field]\n[/details]').should == '1001\n1002\n'
121
- @master.expand_placeholders('[details]Test:[int_field][/details]').should == 'Test:1001Test:1002'
122
- @master.expand_placeholders("[details][int_field]\n[/details]").should == "1001\n1002\n"
117
+ it 'should parse referenced object fields' do
118
+ @detail1.expand_placeholders('[master.string_field]').should == 'foo'
119
+ @detail1.expand_placeholders('[master.string_field] [int_field]').should == 'foo 1001'
120
+ end
123
121
 
124
- @master.expand_placeholders('[details][foo][/details]').should == 'UNKNOWN for Detail: fooUNKNOWN for Detail: foo'
125
- end
122
+ it 'should parse self referenced field' do
123
+ @detail1.expand_placeholders('[detail.int_field]').should == '1001'
124
+ @detail1.expand_placeholders('[detail.master.string_field] [detail.int_field]').should == 'foo 1001'
125
+ end
126
126
 
127
- it 'should expand valid but empty placeholder group with empty string' do
128
- master = Master.new
129
- master.details = []
130
- master.expand_placeholders('[details][int_field][/details]').should == ''
127
+ it 'should parse multiple steps' do
128
+ @detail1.expand_placeholders('[master.side.field]').should == '123'
129
+ end
131
130
  end
132
131
 
133
- it 'should expand single item from a placeholder group' do
134
- @master.details.inspect
135
- @master.expand_placeholders('[details.1.int_field]').should == '1001'
136
- @master.expand_placeholders('[details.2.int_field]').should == '1002'
132
+ context 'with empty fields' do
133
+ it 'should expand valid but empty placeholder group with empty string' do
134
+ master = Master.new
135
+ master.details = []
136
+ master.expand_placeholders('[details][int_field][/details]').should == ''
137
+ end
138
+ it 'should expand single item group with empty string' do
139
+ @master.expand_placeholders('[details.10.int_field]').should == ''
140
+ end
137
141
  end
138
142
 
139
- it 'should expand single item in empty placeholder group with empty string' do
140
- master = Master.new
141
- master.details = []
142
- master.expand_placeholders('[details.0.int_field]').should == ''
143
- end
143
+ context 'with collection' do
144
+ it 'should expand' do
145
+ @master.expand_placeholders('[details][int_field]\n[/details]').should == '1001\n1002\n'
146
+ @master.expand_placeholders('[details]Test:[int_field][/details]').should == 'Test:1001Test:1002'
147
+ @master.expand_placeholders("[details][int_field]\n[/details]").should == "1001\n1002\n"
148
+ @master.expand_placeholders('[details][foo][/details]').should == 'UNKNOWN for Detail: fooUNKNOWN for Detail: foo'
149
+ end
144
150
 
145
- it 'should expand empty single item from placeholder group with empty string' do
146
- @master.expand_placeholders('[details.10.int_field]').should == ''
147
- end
151
+ it 'should expand single item ' do
152
+ @master.expand_placeholders('[details.1.int_field]').should == '1001'
153
+ @master.expand_placeholders('[details.2.int_field]').should == '1002'
154
+ end
148
155
 
149
- it 'should expand placeholder group for non ending group' do
150
- @master.expand_placeholders('[details][int_field]').should == 'END MISSING FOR detailsUNKNOWN for Master: int_field'
156
+ it 'should show error for missing closing' do
157
+ @master.expand_placeholders('[details][int_field]').should == 'END MISSING FOR detailsUNKNOWN for Master: int_field'
158
+ end
151
159
  end
152
160
 
153
- # TODO: Make this possible!
154
- # it "should expand placeholder group if referenced with namespace" do
155
- # @master.expand_placeholders("[master.details][int_field][end]").should == "10011002"
156
- # end
161
+ context 'with custom formatter' do
162
+ before :each do
163
+ I18n.locale = :en_master
164
+ # Thread.current[:default_currency_format] = I18n.t(:'number.currency.format')
165
+ end
157
166
 
158
- it 'should expand placeholders in an array' do
159
- @detail1.expand_placeholders(['[int_field]', '[money_field]', 'static']).should == ['1001', '$12.34', 'static']
167
+ xit 'should format money' do
168
+ @record.expand_placeholders('[money_field]').should == '$12.34'
169
+ end
160
170
  end
161
171
 
162
- it 'should expand placeholders in a hash' do
163
- @detail1.expand_placeholders( :key1 => '[int_field]',
164
- :key2 => '[money_field]',
165
- :key3 => 'static'
166
- ).should ==
167
- { :key1 => '1001',
168
- :key2 => '$12.34',
169
- :key3 => 'static' }
170
- end
172
+
171
173
  end
data/spec/spec_helper.rb CHANGED
@@ -1,20 +1,16 @@
1
1
  # encoding: utf-8
2
2
  $:.unshift(File.dirname(__FILE__))
3
3
  $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
- ENV["RAILS_ENV"] ||= 'test'
5
4
 
6
5
  require 'simplecov'
7
6
  SimpleCov.start do
8
- add_filter "/json/"
7
+ #add_filter "/json/"
9
8
  end
10
9
  SimpleCov.coverage_dir 'coverage'
11
10
 
12
- require 'rubygems'
13
11
  require 'rspec'
14
12
  require 'active_record'
15
13
  require 'king_placeholder'
16
14
 
17
-
18
-
19
15
  RSpec.configure do |config|
20
16
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: king_placeholder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-28 00:00:00.000000000 Z
12
+ date: 2012-07-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: statemachine
@@ -59,22 +59,6 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
- - !ruby/object:Gem::Dependency
63
- name: actionpack
64
- requirement: !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ! '>='
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- type: :runtime
71
- prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ! '>='
76
- - !ruby/object:Gem::Version
77
- version: '0'
78
62
  - !ruby/object:Gem::Dependency
79
63
  name: activerecord
80
64
  requirement: !ruby/object:Gem::Requirement
@@ -189,7 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
173
  version: '0'
190
174
  segments:
191
175
  - 0
192
- hash: 2706534140845333133
176
+ hash: 3312225675603911462
193
177
  required_rubygems_version: !ruby/object:Gem::Requirement
194
178
  none: false
195
179
  requirements:
@@ -198,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
182
  version: '0'
199
183
  segments:
200
184
  - 0
201
- hash: 2706534140845333133
185
+ hash: 3312225675603911462
202
186
  requirements: []
203
187
  rubyforge_project:
204
188
  rubygems_version: 1.8.24