versionist 0.3.1 → 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.
@@ -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: