versionist 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -151,6 +151,52 @@ module Versionist
151
151
  end
152
152
  end
153
153
 
154
+ def copy_helpers
155
+ in_root do
156
+ if File.exists? "app/helpers/#{module_name_for_path(old_module_name)}"
157
+ log "Copying all files from app/helpers/#{module_name_for_path(old_module_name)} to app/helpers/#{module_name_for_path(new_module_name)}"
158
+ FileUtils.cp_r "app/helpers/#{module_name_for_path(old_module_name)}", "app/helpers/#{module_name_for_path(new_module_name)}"
159
+ Dir.glob("app/helpers/#{module_name_for_path(new_module_name)}/*.rb").each do |f|
160
+ text = File.read(f)
161
+ File.open(f, 'w') {|f| f << text.gsub(/#{old_module_name}/, new_module_name)}
162
+ end
163
+ else
164
+ say "No helpers found in app/helpers for #{old_version}"
165
+ end
166
+ end
167
+ end
168
+
169
+ def copy_helper_tests
170
+ in_root do
171
+ case Versionist.configuration.configured_test_framework
172
+ when :test_unit
173
+ if File.exists? "test/helpers/#{module_name_for_path(old_module_name)}"
174
+ log "Copying all files from test/helpers/#{module_name_for_path(old_module_name)} to test/helpers/#{module_name_for_path(new_module_name)}"
175
+ FileUtils.cp_r "test/helpers/#{module_name_for_path(old_module_name)}", "test/helpers/#{module_name_for_path(new_module_name)}"
176
+ Dir.glob("test/helpers/#{module_name_for_path(new_module_name)}/*.rb").each do |f|
177
+ text = File.read(f)
178
+ File.open(f, 'w') {|f| f << text.gsub(/#{old_module_name}/, new_module_name)}
179
+ end
180
+ else
181
+ say "No helper tests found in test/helpers for #{old_version}"
182
+ end
183
+ when :rspec
184
+ if File.exists? "spec/helpers/#{module_name_for_path(old_module_name)}"
185
+ log "Copying all files from spec/helpers/#{module_name_for_path(old_module_name)} to spec/helpers/#{module_name_for_path(new_module_name)}"
186
+ FileUtils.cp_r "spec/helpers/#{module_name_for_path(old_module_name)}", "spec/helpers/#{module_name_for_path(new_module_name)}"
187
+ Dir.glob("spec/helpers/#{module_name_for_path(new_module_name)}/*.rb").each do |f|
188
+ text = File.read(f)
189
+ File.open(f, 'w') {|f| f << text.gsub(/#{old_module_name}/, new_module_name)}
190
+ end
191
+ else
192
+ say "No helper specs found in spec/helpers for #{old_version}"
193
+ end
194
+ else
195
+ say "Unsupported test_framework: #{Versionist.configuration.configured_test_framework}"
196
+ end
197
+ end
198
+ end
199
+
154
200
  def copy_documentation
155
201
  in_root do
156
202
  if File.exists? "public/docs/#{old_version}"
@@ -8,15 +8,47 @@ module Versionist
8
8
 
9
9
  argument :version, :type => :string
10
10
  argument :module_name, :type => :string
11
- argument :versioning_strategy, :banner => "VERSIONING_STRATEGY_OPTIONS", :type => :hash
11
+ class_option :default, :type => :boolean
12
+ class_option :header, :type => :hash, :group => :header
13
+ class_option :parameter, :type => :hash, :group => :parameter
14
+ class_option :path, :type => :hash, :group => :path
15
+ class_option :defaults, :type => :hash, :group => :defaults
16
+
17
+
18
+ def verify_options
19
+ raise "Must specify at least one versioning strategy option" if !['header', 'parameter', 'path'].any? {|strategy| options.has_key?(strategy)}
20
+ if options.has_key?("header")
21
+ raise "Must specify name and value for header versioning strategy" if !options["header"].has_key?("name") || !options["header"].has_key?("value")
22
+ end
23
+ if options.has_key?("parameter")
24
+ raise "Must specify name and value for parameter versioning strategy" if !options["parameter"].has_key?("name") || !options["parameter"].has_key?("value")
25
+ end
26
+ if options.has_key?("path")
27
+ raise "Must specify value for path versioning strategy" if !options["path"].has_key?("value")
28
+ end
29
+ end
12
30
 
13
31
  def add_routes
14
32
  in_root do
15
33
  api_version_block = /api_version.*:module\s*(=>|:)\s*("|')#{module_name_for_route(module_name)}("|')/
16
34
  matching_version_blocks = File.readlines("config/routes.rb").grep(api_version_block)
17
35
  raise "API version already exists in config/routes.rb" if !matching_version_blocks.empty?
18
- versioning_strategy.symbolize_keys!
19
- route "api_version(:module => \"#{module_name_for_route(module_name)}\", #{versioning_strategy.to_s.gsub(/[\{\}]/, '')}) do\n end"
36
+ route_string = "api_version(:module => \"#{module_name_for_route(module_name)}\""
37
+ ['header', 'parameter', 'path'].each do |versioning_strategy|
38
+ if options.has_key?(versioning_strategy)
39
+ options[versioning_strategy].symbolize_keys!
40
+ route_string << ", :#{versioning_strategy} => {#{options[versioning_strategy].to_s.gsub(/[\{\}]/, '').gsub('=>', ' => ')}}"
41
+ end
42
+ end
43
+ if options.has_key?('defaults')
44
+ options['defaults'].symbolize_keys!
45
+ route_string << ", :defaults => {#{options['defaults'].to_s.gsub(/[\{\}]/, '').gsub('=>', ' => ')}}"
46
+ end
47
+ if options.has_key?('default')
48
+ route_string << ", :default => true"
49
+ end
50
+ route_string << ") do\n end"
51
+ route route_string
20
52
  end
21
53
  end
22
54
 
@@ -69,6 +101,25 @@ module Versionist
69
101
  end
70
102
  end
71
103
 
104
+ def add_helpers_dir
105
+ in_root do
106
+ empty_directory "app/helpers/#{module_name_for_path(module_name)}"
107
+ end
108
+ end
109
+
110
+ def add_helpers_test_dir
111
+ in_root do
112
+ case Versionist.configuration.configured_test_framework
113
+ when :test_unit
114
+ empty_directory "test/helpers/#{module_name_for_path(module_name)}"
115
+ when :rspec
116
+ empty_directory "spec/helpers/#{module_name_for_path(module_name)}"
117
+ else
118
+ say "Unsupported test_framework: #{Versionist.configuration.configured_test_framework}"
119
+ end
120
+ end
121
+ end
122
+
72
123
  def add_documentation_base
73
124
  in_root do
74
125
  empty_directory "public/docs/#{version}"
@@ -4,12 +4,14 @@ module Versionist
4
4
  attr_accessor :default_version
5
5
  attr_accessor :header_versions
6
6
  attr_accessor :parameter_versions
7
+ attr_accessor :path_versions
7
8
  attr_accessor :configured_test_framework
8
9
 
9
10
  def initialize
10
11
  @versioning_strategies ||= Array.new
11
12
  @header_versions ||= Array.new
12
13
  @parameter_versions ||= Array.new
14
+ @path_versions ||= Array.new
13
15
  end
14
16
 
15
17
  def clear!
@@ -17,6 +19,7 @@ module Versionist
17
19
  @default_version = nil
18
20
  @header_versions.clear
19
21
  @parameter_versions.clear
22
+ @path_versions.clear
20
23
  end
21
24
  end
22
25
  end
@@ -19,13 +19,16 @@ module Versionist
19
19
 
20
20
  def _call(env)
21
21
  request = ::Rack::Request.new(env)
22
- strategy = Versionist.configuration.versioning_strategies.detect {|vs| vs.is_a?(Versionist::VersioningStrategy::Header) && vs.config[:header] == ACCEPT && env[HTTP_ACCEPT].try(:include?, vs.config[:value])}
22
+ potential_matches = Versionist.configuration.header_versions.select {|hv| hv.config[:header][:name] == ACCEPT && env[HTTP_ACCEPT].try(:include?, hv.config[:header][:value])}
23
+ if !potential_matches.empty?
24
+ strategy = potential_matches.max {|a,b| a.config[:header][:value].length <=> b.config[:header][:value].length}
25
+ end
23
26
  if !strategy.nil?
24
27
  entries = env[HTTP_ACCEPT].split(',')
25
28
  index = -1
26
29
  entries.each_with_index do |e, i|
27
30
  e.strip!
28
- index = i if e == strategy.config[:value]
31
+ index = i if e == strategy.config[:header][:value]
29
32
  end
30
33
  if (index != -1)
31
34
  version = entries.delete_at(index)
@@ -1,25 +1,37 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+
1
3
  module Versionist
2
4
  module Routing
3
5
  # Allows you to constrain routes to specific versions of your api using versioning strategies.
4
6
  # Supported formats:
5
- # api_version(:module => "v1", :header => "Accept", :value => "application/vnd.mycompany.com-v1")
6
- # api_version(:module => "v2__3__4", :path => "/v2.3.4")
7
- # api_version(:module => "v20120317", :parameter => "version", :value => "v20120317")
7
+ #
8
+ # HTTP Header
9
+ # api_version(:module => "V1", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=1"}})
10
+ #
11
+ # Path
12
+ # api_version(:module => "V1", :path => {:value => "v1"}})
13
+ #
14
+ # Request Parameter
15
+ # api_version(:module => "V1", :parameter => {:name => "version", :value => "1"}})
8
16
  #
9
17
  # Specifying default version:
10
- # api_version(:module => "v3__0__0", :header => "API-VERSION", :value => "v3.0.0", :default => true)
18
+ # api_version(:module => "V1", :default => true, :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=1"}})
19
+ #
20
+ # Multiple Strategies per version
21
+ # api_version(:module => "V1", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=1"}, :path => {:value => "v1"})
11
22
  def api_version(config, &block)
12
23
  raise ArgumentError, "you must pass a configuration Hash to api_version" if config.nil? || !config.is_a?(Hash)
13
- raise ArgumentError, "you must specify :module in configuration Hash passed to api_version" if !config.has_key?(:module)
24
+ config.symbolize_keys!
14
25
  raise ArgumentError, "you must specify :header, :path, or :parameter in configuration Hash passed to api_version" if !config.has_key?(:header) && !config.has_key?(:path) && !config.has_key?(:parameter)
15
- raise ArgumentError, ":defaults must be a Hash" if config.has_key?(:defaults) && !config[:defaults].is_a?(Hash)
16
- if config.has_key?(:header)
17
- return configure_header(config, &block)
18
- elsif config.has_key?(:path)
19
- return configure_path(config, &block)
20
- elsif config.has_key?(:parameter)
21
- configure_parameter(config, &block)
26
+ [:header, :path, :parameter].each do |s|
27
+ raise ArgumentError, "#{s} key in configuration Hash passed to api_version must point to a Hash" if config.has_key?(s) && !config[s].is_a?(Hash)
22
28
  end
29
+ raise ArgumentError, "you must specify :module in configuration Hash passed to api_version" if !config.has_key?(:module)
30
+ raise ArgumentError, ":defaults must be a Hash" if config.has_key?(:defaults) && !config[:defaults].is_a?(Hash)
31
+ configure_header(config, &block) if config.has_key?(:header)
32
+ configure_path(config, &block) if config.has_key?(:path)
33
+ configure_parameter(config, &block) if config.has_key?(:parameter)
34
+ configure_default(config, &block) if config.has_key?(:default) && config[:default]
23
35
  end
24
36
 
25
37
 
@@ -33,16 +45,13 @@ module Versionist
33
45
  end
34
46
 
35
47
  def configure_path(config, &block)
36
- config[:path].slice!(0) if config[:path] =~ /^\//
48
+ config[:path][:value].slice!(0) if config[:path][:value] =~ /^\//
37
49
  path = Versionist::VersioningStrategy::Path.new(config)
38
50
  # Use the :as option and strip out non-word characters from the path to avoid this:
39
51
  # https://github.com/rails/rails/issues/3224
40
- route_hash = {:module => config[:module], :as => config[:path].gsub(/\W/, '_')}
52
+ route_hash = {:module => config[:module], :as => config[:path][:value].gsub(/\W/, '_')}
41
53
  route_hash.merge!({:defaults => config[:defaults]}) if config.has_key?(:defaults)
42
- namespace(config[:path], route_hash, &block)
43
- if path.default?
44
- scope(route_hash, &block)
45
- end
54
+ namespace(config[:path][:value], route_hash, &block)
46
55
  end
47
56
 
48
57
  def configure_parameter(config, &block)
@@ -51,5 +60,12 @@ module Versionist
51
60
  route_hash.merge!({:defaults => config[:defaults]}) if config.has_key?(:defaults)
52
61
  scope(route_hash, &block)
53
62
  end
63
+
64
+ def configure_default(config, &block)
65
+ default = Versionist::VersioningStrategy::Default.new(config)
66
+ route_hash = {:module => config[:module], :constraints => default}
67
+ route_hash.merge!({:defaults => config[:defaults]}) if config.has_key?(:defaults)
68
+ scope(route_hash, &block)
69
+ end
54
70
  end
55
71
  end
@@ -1,3 +1,3 @@
1
1
  module Versionist
2
- VERSION = '0.3.1'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -4,31 +4,17 @@ module Versionist
4
4
  module VersioningStrategy
5
5
  class Base
6
6
  attr_reader :config
7
- attr_reader :default
8
7
 
9
8
  def initialize(config={})
10
9
  raise ArgumentError, "you must pass a configuration Hash" if config.nil? || !config.is_a?(Hash)
11
10
  @config = config
12
11
  @config.symbolize_keys!
13
- if @config.has_key?(:default)
14
- @default = true
15
- else
16
- @default = false
17
- end
18
- if !Versionist.configuration.versioning_strategies.include?(self)
19
- raise ArgumentError, "[VERSIONIST] attempt to set more than one default api version" if !Versionist.configuration.default_version.nil? && self.default? && Versionist.configuration.default_version != self
20
- Versionist.configuration.versioning_strategies << self
21
- Versionist.configuration.default_version = self if self.default?
22
- end
23
- end
24
-
25
- def default?
26
- @default
12
+ Versionist.configuration.versioning_strategies << self if !Versionist.configuration.versioning_strategies.include?(self)
27
13
  end
28
14
 
29
15
  def ==(other)
30
16
  return false if other.nil? || !other.is_a?(Versionist::VersioningStrategy::Base)
31
- return self.config == other.config && self.default? == other.default?
17
+ return self.config == other.config
32
18
  end
33
19
  end
34
20
  end
@@ -0,0 +1,37 @@
1
+ module Versionist
2
+ module VersioningStrategy
3
+ # Implements the default version handling strategy.
4
+ class Default < Base
5
+ attr_accessor :strategies
6
+ attr_accessor :module
7
+
8
+ def initialize(config)
9
+ super
10
+ @module = config[:module]
11
+ raise ArgumentError, "[VERSIONIST] attempt to set more than one default api version" if !Versionist.configuration.default_version.nil? && Versionist.configuration.default_version != self
12
+ Versionist.configuration.default_version = self
13
+ end
14
+
15
+ def matches?(request)
16
+ !header_matches?(request) && !parameter_matches?(request)
17
+ end
18
+
19
+ def ==(other)
20
+ super
21
+ return false if !other.is_a?(Versionist::VersioningStrategy::Default)
22
+ return self.module == other.module
23
+ end
24
+
25
+
26
+ private
27
+
28
+ def header_matches?(request)
29
+ Versionist.configuration.header_versions && Versionist.configuration.header_versions.any? {|v| v.matches?(request)}
30
+ end
31
+
32
+ def parameter_matches?(request)
33
+ Versionist.configuration.parameter_versions && Versionist.configuration.parameter_versions.any? {|v| v.matches?(request)}
34
+ end
35
+ end
36
+ end
37
+ end
@@ -4,25 +4,38 @@ module Versionist
4
4
  class Header < Base
5
5
 
6
6
  # Creates a new Header VersioningStrategy object. config must contain the following keys:
7
- # - :header the header to inspect
8
- # - :value the value of the header specifying the version
7
+ # - :header the header hash to inspect
9
8
  def initialize(config)
10
9
  super
11
- raise ArgumentError, "you must specify :header in the configuration Hash" if !config.has_key?(:header)
12
- raise ArgumentError, "you must specify :value in the configuration Hash" if !config.has_key?(:value)
13
- Versionist.configuration.header_versions << config[:value]
10
+ raise ArgumentError, "you must specify :name in the :header configuration Hash" if !config[:header].has_key?(:name)
11
+ raise ArgumentError, "you must specify :value in the :header configuration Hash" if !config[:header].has_key?(:value)
12
+ Versionist.configuration.header_versions << self if !Versionist.configuration.header_versions.include?(self)
14
13
  end
15
14
 
16
15
  def matches?(request)
17
- header_string = request.headers[config[:header]].to_s
18
- return ((!header_string.blank? && header_string.include?(config[:value])) ||
19
- (self.default? && (Versionist.configuration.header_versions.none? {|v| header_string.include?(v)})))
16
+ header_string = request.headers[config[:header][:name]].to_s
17
+ if !header_string.blank?
18
+ potential_matches = Versionist.configuration.header_versions.select {|hv| header_string.include?(hv.config[:header][:value])}
19
+ if !potential_matches.empty?
20
+ if potential_matches.include?(self)
21
+ if potential_matches.size == 1
22
+ return true
23
+ else
24
+ # when finding multiple potential matches, the match with the longest value wins
25
+ # (i.e. v2.1 trumps v2), as one is a subset of the other
26
+ longest = potential_matches.max {|a,b| a.config[:header][:value].length <=> b.config[:header][:value].length}
27
+ return longest == self
28
+ end
29
+ end
30
+ end
31
+ end
32
+ false
20
33
  end
21
34
 
22
35
  def ==(other)
23
36
  super
24
37
  return false if !other.is_a?(Versionist::VersioningStrategy::Header)
25
- return config[:header] == other.config[:header] && self.config[:value] == other.config[:value]
38
+ return config[:header][:name] == other.config[:header][:name] && self.config[:header][:value] == other.config[:header][:value]
26
39
  end
27
40
  end
28
41
  end
@@ -4,25 +4,23 @@ module Versionist
4
4
  class Parameter < Base
5
5
 
6
6
  # Creates a new Parameter VersioningStrategy object. config must contain the following keys:
7
- # - :parameter the parameter to inspect
8
- # - :value the value of the parameter specifying the version
7
+ # - :parameter the parameter hash to inspect
9
8
  def initialize(config)
10
9
  super
11
- raise ArgumentError, "you must specify :parameter in the configuration Hash" if !config.has_key?(:parameter)
12
- raise ArgumentError, "you must specify :value in the configuration Hash" if !config.has_key?(:value)
13
- Versionist.configuration.parameter_versions << config[:value]
10
+ raise ArgumentError, "you must specify :name in the :parameter configuration Hash" if !config[:parameter].has_key?(:name)
11
+ raise ArgumentError, "you must specify :value in the :parameter configuration Hash" if !config[:parameter].has_key?(:value)
12
+ Versionist.configuration.parameter_versions << self if !Versionist.configuration.parameter_versions.include?(self)
14
13
  end
15
14
 
16
15
  def matches?(request)
17
- parameter_string = request.params[config[:parameter]].to_s
18
- return ((!parameter_string.blank? && parameter_string == config[:value]) ||
19
- (self.default? && (Versionist.configuration.parameter_versions.none? {|v| parameter_string.include?(v)})))
16
+ parameter_string = request.params[config[:parameter][:name]].to_s
17
+ return !parameter_string.blank? && parameter_string == config[:parameter][:value]
20
18
  end
21
19
 
22
20
  def ==(other)
23
21
  super
24
22
  return false if !other.is_a?(Versionist::VersioningStrategy::Parameter)
25
- return config[:parameter] == other.config[:parameter] && self.config[:value] == other.config[:value]
23
+ return config[:parameter][:name] == other.config[:parameter][:name] && self.config[:parameter][:value] == other.config[:parameter][:value]
26
24
  end
27
25
  end
28
26
  end
@@ -8,13 +8,14 @@ module Versionist
8
8
  # - :path the path prefix containing the version
9
9
  def initialize(config)
10
10
  super
11
- raise ArgumentError, "you must specify :path in the configuration Hash" if !config.has_key?(:path)
11
+ raise ArgumentError, "you must specify :value in the :path configuration Hash" if !config[:path].has_key?(:value)
12
+ Versionist.configuration.path_versions << self if !Versionist.configuration.path_versions.include?(self)
12
13
  end
13
14
 
14
15
  def ==(other)
15
16
  super
16
17
  return false if !other.is_a?(Versionist::VersioningStrategy::Path)
17
- return config[:path] == other.config[:path]
18
+ return config[:path][:value] == other.config[:path][:value]
18
19
  end
19
20
  end
20
21
  end
@@ -6,5 +6,6 @@ module Versionist
6
6
  autoload :Header, 'versionist/versioning_strategy/header'
7
7
  autoload :Path, 'versionist/versioning_strategy/path'
8
8
  autoload :Parameter, 'versionist/versioning_strategy/parameter'
9
+ autoload :Default, 'versionist/versioning_strategy/default'
9
10
  end
10
11
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: versionist
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.3.1
5
+ version: 1.0.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Brian Ploetz
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2012-07-27 00:00:00 Z
13
+ date: 2013-01-23 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -34,7 +34,7 @@ dependencies:
34
34
  version: "0.7"
35
35
  type: :runtime
36
36
  version_requirements: *id002
37
- description: A plugin for versioning Rails 3 based RESTful APIs.
37
+ description: A plugin for versioning Rails based RESTful APIs.
38
38
  email:
39
39
  executables: []
40
40
 
@@ -74,6 +74,7 @@ files:
74
74
  - lib/versionist/routing.rb
75
75
  - lib/versionist/version.rb
76
76
  - lib/versionist/versioning_strategy/base.rb
77
+ - lib/versionist/versioning_strategy/default.rb
77
78
  - lib/versionist/versioning_strategy/header.rb
78
79
  - lib/versionist/versioning_strategy/parameter.rb
79
80
  - lib/versionist/versioning_strategy/path.rb
@@ -102,10 +103,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
103
  requirements: []
103
104
 
104
105
  rubyforge_project:
105
- rubygems_version: 1.8.17
106
+ rubygems_version: 1.8.24
106
107
  signing_key:
107
108
  specification_version: 3
108
- summary: versionist-0.3.1
109
+ summary: versionist-1.0.0
109
110
  test_files: []
110
111
 
111
112
  has_rdoc: