crystal_ext 0.0.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.
data/Rakefile ADDED
@@ -0,0 +1,66 @@
1
+ require 'rake'
2
+ require 'fileutils'
3
+ current_dir = File.expand_path(File.dirname(__FILE__))
4
+ Dir.chdir current_dir
5
+
6
+
7
+ #
8
+ # Specs
9
+ #
10
+ require 'spec/rake/spectask'
11
+
12
+ task :default => :spec
13
+
14
+ Spec::Rake::SpecTask.new('spec') do |t|
15
+ t.spec_files = FileList["spec/**/*_spec.rb"].select{|f| f !~ /\/_/}
16
+ t.libs = ["#{current_dir}/lib"]
17
+ end
18
+
19
+
20
+ #
21
+ # Gem
22
+ #
23
+ require 'rake/clean'
24
+ require 'rake/gempackagetask'
25
+
26
+ gem_options = {
27
+ :name => "crystal_ext",
28
+ :version => "0.0.4",
29
+ :summary => "Extensions for the Crystal Framework",
30
+ :dependencies => %w(crystal)
31
+ }
32
+
33
+ gem_name = gem_options[:name]
34
+ spec = Gem::Specification.new do |s|
35
+ gem_options.delete(:dependencies).each{|d| s.add_dependency d}
36
+ gem_options.each{|k, v| s.send "#{k}=", v}
37
+
38
+ s.name = gem_name
39
+ s.author = "Alexey Petrushin"
40
+ s.homepage = "http://github.com/alexeypetrushin/#{gem_options[:name]}"
41
+ s.require_path = "lib"
42
+ s.files = (%w{Rakefile readme.md} + Dir.glob("{lib,spec}/**/*"))
43
+
44
+ s.platform = Gem::Platform::RUBY
45
+ s.has_rdoc = true
46
+ end
47
+
48
+ package_dir = "#{current_dir}/build"
49
+ Rake::GemPackageTask.new(spec) do |p|
50
+ p.need_tar = true if RUBY_PLATFORM !~ /mswin/
51
+ p.need_zip = true
52
+ p.package_dir = package_dir
53
+ end
54
+
55
+ task :push do
56
+ # dir = Dir.chdir package_dir do
57
+ gem_file = Dir.glob("#{package_dir}/#{gem_name}*.gem").first
58
+ system "gem push #{gem_file}"
59
+ # end
60
+ end
61
+
62
+ task :clean do
63
+ system "rm -r #{package_dir}"
64
+ end
65
+
66
+ task :release => [:gem, :push, :clean]
@@ -0,0 +1,73 @@
1
+ module Crystal
2
+ JavascriptHelper.class_eval do
3
+
4
+ def javascript_tag_with_defer value = nil, &block
5
+ if @defer_js and !@deferred_js_called
6
+ @deffered_js_initialized.must_be.true
7
+
8
+ value = capture &block if block
9
+
10
+ value = <<END
11
+ deferred_static_scripts.push(function(){
12
+ #{value}
13
+ });
14
+ END
15
+
16
+ value = javascript_tag_without_defer value
17
+ if block
18
+ concat value
19
+ else
20
+ value
21
+ end
22
+ else
23
+ javascript_tag_without_defer value, &block
24
+ end
25
+ end
26
+ alias_method_chain :javascript_tag, :defer
27
+
28
+ def initialize_deferred_js
29
+ return "" unless @defer_js
30
+
31
+ @deffered_js_initialized.must_be.false
32
+ @deffered_js_initialized = true
33
+ dont_defer_js do
34
+ javascript_tag "var deferred_static_scripts = [];"
35
+ end
36
+ end
37
+
38
+ def call_deferred_js
39
+ return "" unless @defer_js
40
+
41
+ @deffered_js_initialized.must_be.true
42
+ @deferred_js_called.must_be.false
43
+ @deferred_js_called = true
44
+ content = <<END
45
+ var i;
46
+ for(i in deferred_static_scripts){deferred_static_scripts[i]()}
47
+ deferred_static_scripts = [];
48
+ END
49
+ dont_defer_js do
50
+ javascript_tag content
51
+ end
52
+ end
53
+
54
+ def dont_defer_js &block
55
+ before = @defer_js
56
+ begin
57
+ @defer_js = false
58
+ block.call
59
+ ensure
60
+ @defer_js = before
61
+ end
62
+ end
63
+
64
+ def defer_js!
65
+ @defer_js.must_be.false
66
+ workspace.params.format.must == 'html'
67
+
68
+ @defer_js = true
69
+ @deferred_js_called = false
70
+ # @defer_static_scripts = workspace.params.format.js? ? false : true
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,37 @@
1
+ # enshure domain has no www. except if there's custom subdomain
2
+ module Crystal
3
+ # class UrlWithWww < StandardError
4
+ # end
5
+
6
+ module Processors
7
+ class EnsureNoWww < Processor
8
+ def call
9
+ workspace.params.must_be.defined
10
+ if workspace.params.html? and url_with_www?
11
+ redirect_without_www
12
+ else
13
+ next_processor.call
14
+ end
15
+ end
16
+
17
+ protected
18
+ def uri
19
+ @uri ||= Uri.parse workspace.request.url
20
+ end
21
+
22
+ def url_with_www?
23
+ uri.host =~ /^www\./
24
+ end
25
+
26
+ def redirect_without_www
27
+ uri.host = uri.host.sub(/^www\./, '')
28
+ url = uri.to_s
29
+
30
+ response = workspace.response
31
+ response.status = 301
32
+ response.headers['Location'] = url
33
+ response.body = %(<html><body>You are being <a href="#{url.html_escape}">redirected</a>.</body></html>)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ # Hack, ActiveSupport currently uses differrent version
2
+ # gem 'i18n', '>= 0.4.1'
3
+ # require 'i18n'
4
+
5
+ require "i18n/backend/pluralization"
6
+ I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
7
+
8
+ dir = File.expand_path(File.dirname(__FILE__))
9
+ I18n.load_path += Dir["#{dir}/i18n/locales/*/*.{rb,yml}"]
10
+
11
+
12
+ #
13
+ # Helpers for Crystal
14
+ #
15
+ [
16
+ :AbstractController,
17
+ :ControllerContext,
18
+ ].each do |klass_name|
19
+ if defined?(Crystal) and Crystal.const_defined?(klass_name)
20
+ klass = Crystal.const_get(klass_name)
21
+ klass.delegate :t, :to => I18n
22
+ end
23
+ end
@@ -0,0 +1,62 @@
1
+ # {
2
+ # :'ru' => {
3
+ # :pluralize => lambda { |n|
4
+ # # Правило плюрализации для русского языка, взято из CLDR, http://unicode.org/cldr/
5
+ # #
6
+ # #
7
+ # # Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
8
+ # #
9
+ # # one -> n mod 10 is 1 and n mod 100 is not 11;
10
+ # # few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
11
+ # # many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
12
+ # # other -> everything else
13
+ # #
14
+ # # Пример
15
+ # #
16
+ # # :one = 1, 21, 31, 41, 51, 61...
17
+ # # :few = 2-4, 22-24, 32-34...
18
+ # # :many = 0, 5-20, 25-30, 35-40...
19
+ # # :other = 1.31, 2.31, 5.31...
20
+ # modulo10 = n.modulo(10)
21
+ # modulo100 = n.modulo(100)
22
+ #
23
+ # if modulo10 == 1 && modulo100 != 11
24
+ # :one
25
+ # elsif (modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)
26
+ # :few
27
+ # elsif modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)
28
+ # :many
29
+ # else
30
+ # :other
31
+ # end
32
+ # }
33
+ # }
34
+ # }
35
+
36
+ {
37
+ :ru => {
38
+ :i18n => {
39
+ :plural => {
40
+ :rule => lambda{|n|
41
+ # Правило плюрализации для русского языка, взято из CLDR, http://unicode.org/cldr/
42
+ #
43
+ #
44
+ # Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
45
+ #
46
+ # one -> n mod 10 is 1 and n mod 100 is not 11;
47
+ # few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
48
+ # many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
49
+ # other -> everything else
50
+ #
51
+ # Пример
52
+ #
53
+ # :one = 1, 21, 31, 41, 51, 61...
54
+ # :few = 2-4, 22-24, 32-34...
55
+ # :many = 0, 5-20, 25-30, 35-40...
56
+ # :other = 1.31, 2.31, 5.31...
57
+ n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,21 @@
1
+ module Crystal
2
+ @plugin_initializers = {}
3
+ class << self
4
+ attr_accessor :plugin_initializers
5
+
6
+ def initialize_plugin type, dir, *args, &block
7
+ type = type.to_s
8
+ plugin_initializers.must.include(type)
9
+
10
+ pi_class = plugin_initializers[type]
11
+ pi = pi_class.new dir, *args
12
+ block.call pi if block
13
+ end
14
+ end
15
+ end
16
+
17
+ require 'crystal_ext/plugin/web'
18
+ crystal.plugin_initializers['web'] = Crystal::Plugin::Web
19
+
20
+ require 'crystal_ext/plugin/app'
21
+ crystal.plugin_initializers['app'] = Crystal::Plugin::App
@@ -0,0 +1,19 @@
1
+ module Crystal
2
+ module Plugin
3
+ class App
4
+ attr_reader :dir
5
+
6
+ def initialize dir
7
+ @dir = dir
8
+ end
9
+
10
+ def plugins list
11
+ plugins = Array.wrap(list)
12
+ # add to $LOAD_PATH
13
+ # plugins.each{|plugin| $LOAD_PATH << "#{dir}/#{plugin}" unless $LOAD_PATH.include?("#{dir}/#{plugin}")}
14
+ # call plugin's init
15
+ plugins.each{|plugin| require "#{plugin}/config/init" if File.exist? "#{dir}/#{plugin}/config/init.rb"}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ module Crystal
2
+ module Plugin
3
+ class Web
4
+ attr_reader :dir
5
+
6
+ def initialize dir
7
+ @dir = dir
8
+ end
9
+
10
+ def asset name
11
+ require 'asset_packager'
12
+ AssetPackager.add "#{dir}/config/asset_packages.yml", "#{dir}/public"
13
+
14
+ crystal.after :environment do
15
+ crystal.ensure_public_symlink name, "#{dir}/public"
16
+ end
17
+ end
18
+
19
+ def routes
20
+ crystal.after :environment do
21
+ routes_file = "#{dir}/config/routes.rb"
22
+ load routes_file if File.exist? routes_file
23
+ end
24
+ end
25
+
26
+ def locales
27
+ I18n.load_path += Dir["#{dir}/config/locales/**/*.{rb,yml}"]
28
+ end
29
+
30
+ def require_paths *relative_paths
31
+ relative_paths = relative_paths.first if relative_paths.first.is_a? Array
32
+ relative_paths.each do |relative_path|
33
+ path = "#{dir}/#{relative_path}"
34
+ $LOAD_PATH << path unless $LOAD_PATH.include? path
35
+ end
36
+ end
37
+
38
+ def autoload *list
39
+ list = list.first if list.first.is_a? Array
40
+ list.each{|d| autoload_dir "#{dir}/#{d}"}
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ Crystal::AbstractController::ClassMethods.class_eval do
2
+ def prepare_model aclass, opt = {}
3
+ id = opt.delete(:id) || :id
4
+
5
+ # variable = opt.delete(:variable) || aclass.model_name.underscore
6
+ opt.must.include :variable
7
+ variable = opt.delete(:variable)
8
+
9
+ finder = opt.delete(:finder) || :find!
10
+
11
+ method = "prepare_#{variable}"
12
+ define_method method do
13
+ model = aclass.send finder, params[id]
14
+ instance_variable_set "@#{variable}", model
15
+ end
16
+ before method, opt
17
+ end
18
+ end
@@ -0,0 +1,75 @@
1
+ require 'crystal_ext/profiles/web_ext_require'
2
+
3
+ #
4
+ # Routes
5
+ #
6
+ Crystal::HttpController.include Crystal::NamedRouter::Helper
7
+ Crystal::ControllerContext.include Crystal::NamedRouter::Helper
8
+
9
+ crystal.after :environment do
10
+ crystal.register(:router, :depends_on => :environment) do
11
+ Crystal::Router.new(:class, [
12
+ [:named_router, Crystal::NamedRouter.new],
13
+ [:default_router, Crystal::DefaultRouter.new]
14
+ ])
15
+ end
16
+ end
17
+
18
+
19
+ #
20
+ # Conveyors
21
+ #
22
+ module Crystal::Processors
23
+ crystal.after :environment do
24
+ crystal.conveyors.web do |web|
25
+ # conveyor
26
+ web.use ConveyorLogger
27
+
28
+ # http
29
+ web.use HttpWriter, :content
30
+ web.use PrepareParams
31
+ web.use EvaluateFormat
32
+ web.use HTTPLogger
33
+
34
+ # forgery protection
35
+ web.use PrepareAutenticityToken
36
+
37
+ # ensure no www
38
+ web.use EnsureNoWww
39
+
40
+ # html
41
+ web.use ScopedParams
42
+
43
+ # controller
44
+ web.use ControllerErrorHandling, :content
45
+
46
+ # router
47
+ web.use Router, :class, :method_name
48
+
49
+ # controller
50
+ web.use ControllerLogger
51
+ web.use ControllerCaller, :content
52
+
53
+ web.build!
54
+ end
55
+ end
56
+ end
57
+
58
+
59
+ #
60
+ # RackAdapter
61
+ #
62
+ Crystal::RackAdapter.build_common_app do |builder|
63
+ config = crystal.config
64
+
65
+ # CommonLogger, ShowExceptions, Lint
66
+ builder.use Rack::Lint if config.development?
67
+ builder.use Crystal::StaticFiles, "#{config.root!}/public" if config.static? and config.root? and config.development?
68
+
69
+ # use Rack::Session::Cookie, :key => 'rack.session', :domain => 'foo.com', :path => '/', :expire_after => 2592000, :secret => 'change_me'
70
+ builder.use Rack::Session::Cookie, config.session.to_hash if config.session?
71
+
72
+ # builder.use Rack::CommonLogger
73
+ builder.use Rack::MethodOverride
74
+ # builder.use ShowExceptions if config.show_exceptions?
75
+ end
@@ -0,0 +1,13 @@
1
+ # crystal
2
+ require 'crystal/profiles/web_require'
3
+
4
+ # crystal_ext
5
+ %w(
6
+ defer_js
7
+ i18n
8
+ prepare_model
9
+ user_error
10
+ ensure_no_www
11
+ protect_from_forgery
12
+ plugin
13
+ ).each{|f| require "crystal_ext/#{f}"}
@@ -0,0 +1,64 @@
1
+ #
2
+ # Processor
3
+ #
4
+ module Crystal
5
+ module Processors
6
+ class PrepareAutenticityToken < Processor
7
+ def call
8
+ if config.session?
9
+ request = workspace.request.must_be.defined
10
+ params = workspace.params.must_be.defined
11
+
12
+ token = request.session['authenticity_token'] || params['session_authenticity_token']
13
+
14
+ if token.blank? and request.get? and
15
+ token = generate_authenticity_token
16
+ request.session['authenticity_token'] = token
17
+ end
18
+
19
+ workspace.session_authenticity_token = token
20
+ end
21
+
22
+ next_processor.call
23
+ end
24
+
25
+
26
+ protected
27
+ def generate_authenticity_token
28
+ ActiveSupport::SecureRandom.base64(32)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+ #
36
+ # Remote
37
+ #
38
+ Crystal::HttpController.class_eval do
39
+ BROWSER_GENERATED_TYPES = %w(html url_encoded_form multipart_form text).to_set
40
+
41
+ protected
42
+ def protect_from_forgery &block
43
+ request = workspace.request
44
+ allow = (
45
+ request.get? or
46
+ # TODO2 check content_type is lovercased
47
+ !BROWSER_GENERATED_TYPES.include?(request.content_type) or
48
+ (workspace.session_authenticity_token.present? and
49
+ workspace.session_authenticity_token == params.authenticity_token)
50
+ )
51
+
52
+ if allow
53
+ block.call
54
+ else
55
+ raise "Invalid authenticity token!"
56
+ end
57
+ end
58
+ end
59
+
60
+ Crystal::HttpController::ClassMethods.class_eval do
61
+ def protect_from_forgery options = {}
62
+ around :protect_from_forgery, options
63
+ end
64
+ end
@@ -0,0 +1,12 @@
1
+ class UserError < StandardError
2
+ end
3
+
4
+ Object.class_eval do
5
+ def raise_user_error msg
6
+ raise UserError, msg
7
+ end
8
+
9
+ def self.raise_user_error msg
10
+ raise UserError, msg
11
+ end
12
+ end
data/readme.md ADDED
@@ -0,0 +1,14 @@
1
+ # Extensions for the Crystal framework.
2
+
3
+ - abstract_interface - tools for rapid interface creation.
4
+ - common_interface - concrete interface built with abstract_interface tools.
5
+ - asset_packager - packages CSS and JS asset for your application.
6
+ - crystal_ext - additions and extensions of the Crystal.
7
+ - crystal_jquery - jQuery plugin for the Crystal.
8
+ - mongo_mapper_ext - additions and extensions of the MongoMapper.
9
+ - rad - Rapid Application Development tool built upon of the Crystal.
10
+
11
+ Please go to specs for docs.
12
+
13
+ P.S.
14
+ All these components will be in standalone gems, but right now it's simpler for me to hold all of them in one place.
@@ -0,0 +1,19 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/helper"
2
+
3
+ require 'crystal/profiles/web_require'
4
+
5
+ require 'crystal/spec'
6
+
7
+ Spec::Example::ExampleGroup.class_eval do
8
+ def self.with_controller_ext_spec
9
+ before :all do
10
+ crystal.after :environment do
11
+ crystal.register(:router, :depends_on => :environment){Crystal::Router.new(:class, [Crystal::DefaultRoute.new])}
12
+
13
+ crystal.conveyors.web do |web|
14
+ web.use Crystal::Processors::ControllerCaller, :content
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/controller_helper"
2
+ require 'crystal/html'
3
+ require 'crystal_ext/defer_js'
4
+
5
+ describe "Defer JS" do
6
+ before :each do
7
+ class ContextStub
8
+ include Crystal::BasicHtmlHelper, Crystal::JavascriptHelper
9
+
10
+ def workspace
11
+ @workspace ||= {
12
+ :params => {
13
+ :format => 'html'
14
+ }.to_openobject
15
+ }.to_openobject
16
+ end
17
+ end
18
+
19
+ @t = ContextStub.new
20
+ end
21
+
22
+ it "should work as usual if deffered mode not enabled" do
23
+ @t.javascript_tag("script").should == %(<script type="text/javascript">script</script>)
24
+ end
25
+
26
+ it "deffered mode" do
27
+ @t.defer_js!
28
+ @t.initialize_deferred_js.should =~ /var deferred_static_scripts/
29
+ @t.javascript_tag('script').should =~ /deferred_static_scripts/
30
+ @t.call_deferred_js.should =~ /deferred_static_scripts/
31
+ end
32
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,5 @@
1
+ dir = File.expand_path(File.dirname(__FILE__))
2
+ lib_dir = File.expand_path("#{dir}/../../../lib")
3
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include? lib_dir
4
+
5
+ require 'spec_ext'
data/spec/i18n_spec.rb ADDED
@@ -0,0 +1,30 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/helper"
2
+ require 'crystal/support' # to check that i18n will correctly works with ActiveSupport
3
+ require 'crystal_ext/i18n'
4
+
5
+ describe 'I18n' do
6
+ before :all do
7
+ dir = File.expand_path(File.dirname(__FILE__))
8
+ I18n.load_path += Dir["#{dir}/i18n_spec/locales/*/*.{rb,yml}"]
9
+ end
10
+
11
+ def t *args
12
+ I18n.t *args
13
+ end
14
+
15
+ it "basic" do
16
+ I18n.locale = 'en'
17
+ t(:name).should == "Name"
18
+ t(:name).is_a?(String).should be_true
19
+
20
+ I18n.locale = 'ru'
21
+ t(:name).should == "Имя"
22
+ end
23
+
24
+ it "pluggable pluralization" do
25
+ I18n.locale = 'ru'
26
+ t(:comments_count, :count => 1).should == "1 комментарий"
27
+ t(:comments_count, :count => 2).should == "2 комментария"
28
+ t(:comments_count, :count => 5).should == "5 комментариев"
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ en:
2
+ name: "Name"
3
+ comments:
4
+ one: "%{count} comment"
5
+ many: "%{count} comments"
@@ -0,0 +1,7 @@
1
+ ru:
2
+ name: "Имя"
3
+ comments_count:
4
+ one: "%{count} комментарий"
5
+ few: "%{count} комментария"
6
+ many: "%{count} комментариев"
7
+ other: "%{count} комментариев"
@@ -0,0 +1,42 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/controller_helper"
2
+ require 'crystal_ext/prepare_model'
3
+
4
+ describe "User Error" do
5
+ with_environment :test
6
+ with_controller_ext_spec
7
+
8
+ before :all do
9
+ class ::SomeModel
10
+ def self.find! id
11
+ id.should == 'some id'
12
+ SomeModel.new
13
+ end
14
+ end
15
+
16
+ # crystal[:config].environment = :test
17
+ # crystal[:environment] = Crystal::Environment.new
18
+ # crystal[:environment].load :controller_ext_spec
19
+ end
20
+
21
+ after :all do
22
+ remove_constants %w(SomeModel ControllerSpec)
23
+
24
+ # Micon.clear
25
+ end
26
+
27
+ it "user error" do
28
+ class ::ControllerSpec
29
+ inherit Crystal::HttpController
30
+
31
+ prepare_model SomeModel, :id => :some_model, :variable => 'some_model'
32
+
33
+ def action
34
+ @some_model.should_not == nil
35
+ render :inline => 'ok'
36
+ end
37
+ end
38
+
39
+ ccall(ControllerSpec, :action, :some_model => 'some id').content.should == 'ok'
40
+ # workspace.response.should == [200, {"Content-Type"=>"application/json"}, %({"result":true})]
41
+ end
42
+ end
@@ -0,0 +1,169 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/helper"
2
+
3
+ require 'crystal/profiles/web_require'
4
+ require 'crystal_ext/protect_from_forgery'
5
+
6
+ require 'crystal/spec/environment'
7
+
8
+ describe "Forgery protection" do
9
+ with_environment :test
10
+
11
+ before :all do
12
+ class ForgerySpecHelper < Crystal::Processor
13
+ def call
14
+ block = workspace.check_forgery.before_request
15
+ block.call workspace if block
16
+ workspace.before_request_done = true
17
+
18
+ next_processor.call
19
+
20
+ block = workspace.check_forgery.after_request
21
+ block.call workspace if block
22
+ workspace.after_request_done = true
23
+ end
24
+ end
25
+
26
+ class ::AnRemote
27
+ inherit Crystal::HttpController
28
+
29
+ protect_from_forgery :only => :protected_method
30
+
31
+ def protected_method
32
+ render :inline => 'protected result'
33
+ end
34
+
35
+ def method_without_protection
36
+ render :inline => 'result'
37
+ end
38
+
39
+ def dumb_method; end
40
+ end
41
+
42
+ crystal.after :environment do
43
+ crystal.conveyors.web do |web|
44
+ web.use Crystal::Processors::PrepareParams
45
+ web.use Crystal::Processors::EvaluateFormat
46
+ web.use ForgerySpecHelper
47
+ web.use Crystal::Processors::PrepareAutenticityToken
48
+ web.use Crystal::Processors::ControllerCaller, :content
49
+ end
50
+
51
+ crystal.config.session = {'key' => 'session_id'}
52
+ end
53
+ end
54
+
55
+ after :all do
56
+ remove_constants %w(AnRemote ForgerySpecHelper)
57
+ end
58
+
59
+ def check_forgery opt
60
+ workspace = nil
61
+ result = Crystal::HTTPAdapter.mock_call({}, :check_forgery => opt.to_openobject) do |&block|
62
+ block.call
63
+ workspace = crystal[:workspace]
64
+ end
65
+
66
+ workspace.before_request_done.should be_true
67
+ workspace.after_request_done.should be_true
68
+ workspace
69
+ end
70
+
71
+ it "should set :authenticity_token only for :get and :html request" do
72
+ check_forgery(
73
+ :before_request => lambda{|workspace|
74
+ workspace.env['REQUEST_METHOD'] = 'GET'
75
+ workspace.env['CONTENT_TYPE'] = 'html'
76
+ workspace.class = AnRemote
77
+ workspace.method_name = 'dumb_method'
78
+ },
79
+ :after_request => lambda{|workspace|
80
+ workspace.request.session['authenticity_token'].should_not be_blank
81
+ }
82
+ )
83
+
84
+ # post
85
+ check_forgery(
86
+ :before_request => lambda{|workspace|
87
+ workspace.env['REQUEST_METHOD'] = 'POST'
88
+ workspace.env['CONTENT_TYPE'] = 'html'
89
+ workspace.class = AnRemote
90
+ workspace.method_name = 'dumb_method'
91
+ },
92
+ :after_request => lambda{|workspace|
93
+ workspace.request.session['authenticity_token'].should be_blank
94
+ }
95
+ )
96
+ end
97
+
98
+ it "should check any non :get request with browser's formats for :authenticity_token" do
99
+ lambda{
100
+ check_forgery(
101
+ :before_request => lambda{|workspace|
102
+ workspace.env['REQUEST_METHOD'] = 'POST'
103
+ workspace.env['CONTENT_TYPE'] = 'html'
104
+ workspace.class = AnRemote
105
+ workspace.method_name = 'protected_method'
106
+ }
107
+ )
108
+ }.should raise_error(/Invalid authenticity token/)
109
+
110
+ # with correct :authenticity_token
111
+ check_forgery(
112
+ :before_request => lambda{|workspace|
113
+ workspace.env['REQUEST_METHOD'] = 'POST'
114
+ workspace.env['CONTENT_TYPE'] = 'html'
115
+ workspace.request.session['authenticity_token'] = 'secure token'
116
+ workspace.params['authenticity_token'] = 'secure token'
117
+ workspace.class = AnRemote
118
+ workspace.method_name = 'protected_method'
119
+ },
120
+ :after_request => lambda{|workspace|
121
+ workspace.content.should == "protected result"
122
+ }
123
+ )
124
+
125
+ # non-browser format
126
+ check_forgery(
127
+ :before_request => lambda{|workspace|
128
+ workspace.env['REQUEST_METHOD'] = 'POST'
129
+ workspace.env['CONTENT_TYPE'] = 'non-browser-format'
130
+ workspace.class = AnRemote
131
+ workspace.method_name = 'protected_method'
132
+ },
133
+ :after_request => lambda{|workspace|
134
+ workspace.content.should == "protected result"
135
+ }
136
+ )
137
+ end
138
+
139
+ it "should not protect non protected methods" do
140
+ check_forgery(
141
+ :before_request => lambda{|workspace|
142
+ workspace.env['REQUEST_METHOD'] = 'POST'
143
+ workspace.env['CONTENT_TYPE'] = 'html'
144
+ workspace.class = AnRemote
145
+ workspace.method_name = 'method_without_protection'
146
+ },
147
+ :after_request => lambda{|workspace|
148
+ workspace.content.should == "result"
149
+ }
150
+ )
151
+ end
152
+
153
+ it "should use :session_authenticity_token from params (for flash support)" do
154
+ check_forgery(
155
+ :before_request => lambda{|workspace|
156
+ workspace.env['REQUEST_METHOD'] = 'POST'
157
+ workspace.params.format = 'html'
158
+ workspace.params['session_authenticity_token'] = 'secure token'
159
+ workspace.params['authenticity_token'] = 'secure token'
160
+ workspace.class = AnRemote
161
+ workspace.method_name = 'protected_method'
162
+ },
163
+ :after_request => lambda{|workspace|
164
+ workspace.content.should == "protected result"
165
+ }
166
+ )
167
+ end
168
+
169
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,41 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/controller_helper"
2
+ require 'crystal_ext/user_error'
3
+ require 'crystal_ext/controller_helper'
4
+
5
+ describe "User Error" do
6
+ with_environment :test
7
+ with_controller_ext_spec
8
+
9
+ # before :all do
10
+ # crystal[:config].environment = :test
11
+ # crystal[:environment] = Crystal::Environment.new
12
+ # crystal[:environment].load :controller_ext_spec
13
+ # end
14
+
15
+ after :all do
16
+ # Micon.clear
17
+ remove_constants %w(UserErrorSpec)
18
+ end
19
+
20
+ it "user error" do
21
+ class ::UserErrorSpec
22
+ inherit Crystal::HttpController
23
+
24
+ def call
25
+ raise_user_error "some error"
26
+ end
27
+
28
+ protected
29
+ def catch_user_error
30
+ begin
31
+ yield
32
+ rescue UserError => ue
33
+ render :inline => "Catched #{ue.message}"
34
+ end
35
+ end
36
+ around :catch_user_error
37
+ end
38
+
39
+ ccall(UserErrorSpec, :call).content.should == "Catched some error"
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crystal_ext
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 4
10
+ version: 0.0.4
11
+ platform: ruby
12
+ authors:
13
+ - Alexey Petrushin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-11 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: crystal
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description:
36
+ email:
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - Rakefile
45
+ - readme.md
46
+ - lib/crystal_ext/defer_js.rb
47
+ - lib/crystal_ext/ensure_no_www.rb
48
+ - lib/crystal_ext/i18n/locales/ru/pluralization.rb
49
+ - lib/crystal_ext/i18n.rb
50
+ - lib/crystal_ext/plugin/app.rb
51
+ - lib/crystal_ext/plugin/web.rb
52
+ - lib/crystal_ext/plugin.rb
53
+ - lib/crystal_ext/prepare_model.rb
54
+ - lib/crystal_ext/profiles/web_ext.rb
55
+ - lib/crystal_ext/profiles/web_ext_require.rb
56
+ - lib/crystal_ext/protect_from_forgery.rb
57
+ - lib/crystal_ext/user_error.rb
58
+ - spec/controller_helper.rb
59
+ - spec/defer_js_spec.rb
60
+ - spec/helper.rb
61
+ - spec/i18n_spec/locales/en/general.yml
62
+ - spec/i18n_spec/locales/ru/general.yml
63
+ - spec/i18n_spec.rb
64
+ - spec/prepare_model_spec.rb
65
+ - spec/protect_from_forgery_spec.rb
66
+ - spec/spec.opts
67
+ - spec/user_error_spec.rb
68
+ has_rdoc: true
69
+ homepage: http://github.com/alexeypetrushin/crystal_ext
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options: []
74
+
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project:
98
+ rubygems_version: 1.3.7
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Extensions for the Crystal Framework
102
+ test_files: []
103
+