crystal_ext 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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
+