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 +66 -0
- data/lib/crystal_ext/defer_js.rb +73 -0
- data/lib/crystal_ext/ensure_no_www.rb +37 -0
- data/lib/crystal_ext/i18n.rb +23 -0
- data/lib/crystal_ext/i18n/locales/ru/pluralization.rb +62 -0
- data/lib/crystal_ext/plugin.rb +21 -0
- data/lib/crystal_ext/plugin/app.rb +19 -0
- data/lib/crystal_ext/plugin/web.rb +44 -0
- data/lib/crystal_ext/prepare_model.rb +18 -0
- data/lib/crystal_ext/profiles/web_ext.rb +75 -0
- data/lib/crystal_ext/profiles/web_ext_require.rb +13 -0
- data/lib/crystal_ext/protect_from_forgery.rb +64 -0
- data/lib/crystal_ext/user_error.rb +12 -0
- data/readme.md +14 -0
- data/spec/controller_helper.rb +19 -0
- data/spec/defer_js_spec.rb +32 -0
- data/spec/helper.rb +5 -0
- data/spec/i18n_spec.rb +30 -0
- data/spec/i18n_spec/locales/en/general.yml +5 -0
- data/spec/i18n_spec/locales/ru/general.yml +7 -0
- data/spec/prepare_model_spec.rb +42 -0
- data/spec/protect_from_forgery_spec.rb +169 -0
- data/spec/spec.opts +4 -0
- data/spec/user_error_spec.rb +41 -0
- metadata +103 -0
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,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
|
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
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,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,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
|
+
|