mholling-active_url 0.1.1 → 0.1.2
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/README.textile +1 -1
- data/VERSION.yml +1 -1
- data/lib/active_url/configuration.rb +3 -10
- data/lib/active_url/crypto.rb +2 -2
- data/spec/belongs_to_spec.rb +90 -0
- data/spec/crypto_spec.rb +27 -0
- data/spec/instance_spec.rb +120 -0
- data/spec/validations_spec.rb +140 -0
- metadata +10 -4
- data/spec/active_url_spec.rb +0 -358
data/README.textile
CHANGED
|
@@ -28,7 +28,7 @@ config.gem "mholling-active_url", :lib => "active_url", :source => "http://gems.
|
|
|
28
28
|
Specify a secret passphrase for the library to perform its encryption. You can set this by adding an initializer (say active_url.rb) in your config/initializers directory. This will just set the secret passphrase for your app (you might not want to check this into your source control):
|
|
29
29
|
|
|
30
30
|
<pre>
|
|
31
|
-
ActiveUrl.
|
|
31
|
+
ActiveUrl::Config.secret = "my-app-encryption-secret"
|
|
32
32
|
</pre>
|
|
33
33
|
|
|
34
34
|
To generate secret URLs in your Rails application, simply inherit a model from <code>ActiveUrl::Base</code>, in the same way you would normally inherit from <code>ActiveRecord::Base</code>. These objects won't be stored in your database; instead they will be persisted as an encrypted ID and placed in an URL given only to that user (typically by email).
|
data/VERSION.yml
CHANGED
data/lib/active_url/crypto.rb
CHANGED
|
@@ -23,9 +23,9 @@ module ActiveUrl
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
25
|
def self.start(mode)
|
|
26
|
-
raise ::ArgumentError.new("Set a secret key using ActiveUrl.
|
|
26
|
+
raise ::ArgumentError.new("Set a secret key using ActiveUrl::Config.secret = 'your-secret'") if ActiveUrl::Config.secret.blank?
|
|
27
27
|
crypto = OpenSSL::Cipher::Cipher.new('aes-256-ecb').send(mode)
|
|
28
|
-
crypto.key = Digest::SHA256.hexdigest(ActiveUrl.
|
|
28
|
+
crypto.key = Digest::SHA256.hexdigest(ActiveUrl::Config.secret)
|
|
29
29
|
return crypto
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe ActiveUrl do
|
|
4
|
+
before(:each) do
|
|
5
|
+
ActiveUrl::Config.stub!(:secret).and_return("secret")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
context "instance with belongs_to association" do
|
|
9
|
+
before(:all) do
|
|
10
|
+
# a simple pretend-ActiveRecord model for testing belongs_to without setting up a db:
|
|
11
|
+
class ::User < ActiveRecord::Base
|
|
12
|
+
def self.columns() @columns ||= []; end
|
|
13
|
+
def self.column(name, sql_type = nil, default = nil, null = true)
|
|
14
|
+
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Secret < ActiveUrl::Base
|
|
19
|
+
belongs_to :user
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
before(:each) do
|
|
24
|
+
@url = Secret.new
|
|
25
|
+
@user = User.new
|
|
26
|
+
@user.stub!(:id).and_return(1)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "should raise ArgumentError if the association name is not an ActiveRecord class" do
|
|
30
|
+
lambda { Secret.belongs_to :foo }.should raise_error(ArgumentError)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should respond to association_id, association_id=, association & association=" do
|
|
34
|
+
@url.attribute_names.should include(:user_id)
|
|
35
|
+
@url.should respond_to(:user)
|
|
36
|
+
@url.should respond_to(:user=)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should have nil association if association or association_id not set" do
|
|
40
|
+
@url.user.should be_nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "should not allow mass assignment of association_id" do
|
|
44
|
+
@url = Secret.new(:user_id => @user.id)
|
|
45
|
+
@url.user_id.should be_nil
|
|
46
|
+
@url.user.should be_nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "should not allow mass assignment of association" do
|
|
50
|
+
@url = Secret.new(:user => @user)
|
|
51
|
+
@url.user_id.should be_nil
|
|
52
|
+
@url.user.should be_nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "should be able to have its association set to nil" do
|
|
56
|
+
@url.user_id = @user.id
|
|
57
|
+
@url.user = nil
|
|
58
|
+
@url.user_id.should be_nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "should raise ArgumentError if association is set to wrong type" do
|
|
62
|
+
lambda { @url.user = Object.new }.should raise_error(TypeError)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should find its association_id if association is set" do
|
|
66
|
+
@url.user = @user
|
|
67
|
+
@url.user_id.should == @user.id
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "should find its association if association_id is set" do
|
|
71
|
+
User.should_receive(:find).with(@user.id).and_return(@user)
|
|
72
|
+
@url.user_id = @user.id
|
|
73
|
+
@url.user.should == @user
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "should return nil association if association_id is unknown" do
|
|
77
|
+
User.should_receive(:find).and_raise(ActiveRecord::RecordNotFound)
|
|
78
|
+
@url.user_id = 10
|
|
79
|
+
@url.user.should be_nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "should know its association when found by id" do
|
|
83
|
+
User.should_receive(:find).with(@user.id).and_return(@user)
|
|
84
|
+
@url.user_id = @user.id
|
|
85
|
+
@url.save
|
|
86
|
+
@found = Secret.find(@url.id)
|
|
87
|
+
@found.user.should == @user
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/spec/crypto_spec.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe ActiveUrl do
|
|
4
|
+
before(:each) do
|
|
5
|
+
ActiveUrl::Config.stub!(:secret).and_return("secret")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe "crypto" do
|
|
9
|
+
it "should raise ArgumentError when no secret is set" do
|
|
10
|
+
ActiveUrl::Config.stub!(:secret).and_return(nil)
|
|
11
|
+
lambda { ActiveUrl::Crypto.encrypt("clear") }.should raise_error(ArgumentError)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "should decode what it encodes" do
|
|
15
|
+
ActiveUrl::Crypto.decrypt(ActiveUrl::Crypto.encrypt("clear")).should == "clear"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "should always yield URL-safe output characters" do
|
|
19
|
+
url_safe = /^[\w\-]*$/
|
|
20
|
+
(1..20).each do |n|
|
|
21
|
+
clear = (0...8).inject("") { |string, n| string << rand(255).chr } # random string
|
|
22
|
+
cipher = ActiveUrl::Crypto.encrypt(clear)
|
|
23
|
+
cipher.should =~ url_safe
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe ActiveUrl do
|
|
4
|
+
before(:each) do
|
|
5
|
+
ActiveUrl::Config.stub!(:secret).and_return("secret")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
context "instance" do
|
|
9
|
+
before(:each) do
|
|
10
|
+
@url = ActiveUrl::Base.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should have nil id" do
|
|
14
|
+
@url.id.should be_nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "should be a new_record" do
|
|
18
|
+
@url.should be_new_record
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should be saveable" do
|
|
22
|
+
@url.save.should be_true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "after saving" do
|
|
26
|
+
before(:each) do
|
|
27
|
+
@url.save
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "should have an id" do
|
|
31
|
+
@url.id.should_not be_blank
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "should not be a new record" do
|
|
35
|
+
@url.should_not be_new_record
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "derived" do
|
|
41
|
+
before(:all) do
|
|
42
|
+
class DerivedClass < ActiveUrl::Base
|
|
43
|
+
attribute :foo, :bar
|
|
44
|
+
attribute :baz, :accessible => true
|
|
45
|
+
attr_accessible :bar
|
|
46
|
+
|
|
47
|
+
attr_accessor :x, :y
|
|
48
|
+
attr_accessible :y
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "instance" do
|
|
53
|
+
it "should not mass-assign attributes by default" do
|
|
54
|
+
@url = DerivedClass.new(:foo => "foo")
|
|
55
|
+
@url.foo.should be_nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should mass-assign attributes declared as attr_accessible" do
|
|
59
|
+
@url = DerivedClass.new(:bar => "bar")
|
|
60
|
+
@url.bar.should == "bar"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "should mass-assigned attributes with :accessible specified on declaration" do
|
|
64
|
+
@url = DerivedClass.new(:baz => "baz")
|
|
65
|
+
@url.baz.should == "baz"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "should not mass-assign virtual attributes by default" do
|
|
69
|
+
@url = DerivedClass.new(:x => "x")
|
|
70
|
+
@url.x.should be_nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "should mass-assign its accessible virtual attributes" do
|
|
74
|
+
@url = DerivedClass.new(:y => "y")
|
|
75
|
+
@url.y.should == "y"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "should know its mass-assignable attribute names" do
|
|
79
|
+
@url = DerivedClass.new
|
|
80
|
+
[ :bar, :baz, :y ].each { |name| @url.accessible_attributes.should include(name) }
|
|
81
|
+
[ :foo, :x ].each { |name| @url.accessible_attributes.should_not include(name) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "should know its attribute names" do
|
|
85
|
+
@url = DerivedClass.new
|
|
86
|
+
[ :foo, :bar, :baz ].each { |name| @url.attribute_names.should include(name) }
|
|
87
|
+
[ :x, :y ].each { |name| @url.attribute_names.should_not include(name) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context "equality" do
|
|
91
|
+
before(:all) do
|
|
92
|
+
class OtherClass < DerivedClass
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "should be based on class and attributes only" do
|
|
97
|
+
@url = DerivedClass.new(:bar => "bar", :baz => "baz")
|
|
98
|
+
@url2 = DerivedClass.new(:bar => "bar", :baz => "baz")
|
|
99
|
+
@url3 = DerivedClass.new(:bar => "BAR", :baz => "baz")
|
|
100
|
+
@url4 = OtherClass.new(:bar => "bar", :baz => "baz")
|
|
101
|
+
@url.should == @url2
|
|
102
|
+
@url.should_not == @url3
|
|
103
|
+
@url.should_not == @url4
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context "class" do
|
|
109
|
+
it "should know its mass-assignable attribute names" do
|
|
110
|
+
[ :bar, :baz, :y ].each { |name| DerivedClass.accessible_attributes.should include(name) }
|
|
111
|
+
[ :foo, :x ].each { |name| DerivedClass.accessible_attributes.should_not include(name) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "should know its attribute names" do
|
|
115
|
+
[ :foo, :bar, :baz ].each { |name| DerivedClass.attribute_names.should include(name) }
|
|
116
|
+
[ :x, :y ].each { |name| DerivedClass.attribute_names.should_not include(name) }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe ActiveUrl do
|
|
4
|
+
before(:each) do
|
|
5
|
+
ActiveUrl::Config.stub!(:secret).and_return("secret")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
context "instance with validations" do
|
|
9
|
+
before(:all) do
|
|
10
|
+
class Registration < ActiveUrl::Base
|
|
11
|
+
attribute :name, :email, :password, :age, :accessible => true
|
|
12
|
+
validates_presence_of :name
|
|
13
|
+
validates_format_of :email, :with => /^[\w\.=-]+@[\w\.-]+\.[a-zA-Z]{2,4}$/ix
|
|
14
|
+
validates_length_of :password, :minimum => 8
|
|
15
|
+
validates_numericality_of :age
|
|
16
|
+
after_save :send_registration_email
|
|
17
|
+
|
|
18
|
+
def send_registration_email
|
|
19
|
+
@sent = true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "when invalid" do
|
|
25
|
+
before(:each) do
|
|
26
|
+
@registration = Registration.new(:email => "user @ example . com", :password => "short", :age => "ten")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "should not validate" do
|
|
30
|
+
@registration.should_not be_valid
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should not save" do
|
|
34
|
+
@registration.save.should_not be_true
|
|
35
|
+
@registration.id.should be_nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "should raise ActiveUrl::InvalidRecord when saved with bang" do
|
|
39
|
+
lambda { @registration.save! }.should raise_error(ActiveUrl::RecordInvalid)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context "and saved" do
|
|
43
|
+
before(:each) do
|
|
44
|
+
@registration.save
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "should have errors" do
|
|
48
|
+
@registration.errors.should_not be_empty
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "should validate presence of an attribute" do
|
|
52
|
+
@registration.errors[:name].should_not be_blank
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "should validate format of an attribute" do
|
|
56
|
+
@registration.errors[:email].should_not be_blank
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "should validate length of an attribute" do
|
|
60
|
+
@registration.errors[:password].should_not be_nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "should validate numericality of an attribute" do
|
|
64
|
+
@registration.errors[:age].should_not be_nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "should not execute any after_save callbacks" do
|
|
68
|
+
@registration.instance_variables.should_not include("@sent")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context "when valid" do
|
|
74
|
+
before(:each) do
|
|
75
|
+
@registration = Registration.new(:name => "John Doe", :email => "user@example.com", :password => "password", :age => "10")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "should validate" do
|
|
79
|
+
@registration.should be_valid
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context "and saved" do
|
|
83
|
+
before(:each) do
|
|
84
|
+
@registration.save
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "should have an id" do
|
|
88
|
+
@registration.id.should_not be_blank
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "should have a param equal to its id" do
|
|
92
|
+
@registration.id.should == @registration.to_param
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "should execute any after_save callbacks" do
|
|
96
|
+
@registration.instance_variables.should include("@sent")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context "and re-found by its class" do
|
|
100
|
+
before(:each) do
|
|
101
|
+
@found = Registration.find(@registration.id)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "should exist" do
|
|
105
|
+
@found.should_not be_nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "should have the same id" do
|
|
109
|
+
@found.id.should == @registration.id
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "should have the same attributes" do
|
|
113
|
+
@found.attributes.should == @registration.attributes
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "should be valid" do
|
|
117
|
+
@found.should be_valid
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
context "and subsequently made invalid" do
|
|
122
|
+
before(:each) do
|
|
123
|
+
@registration.password = "short"
|
|
124
|
+
@registration.stub!(:valid?).and_return(true)
|
|
125
|
+
@registration.save
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "should not be found by its class" do
|
|
129
|
+
@registration.id.should_not be_blank
|
|
130
|
+
lambda { Registration.find(@registration.id) }.should raise_error(ActiveUrl::RecordNotFound)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "should raise ActiveUrl::RecordNotFound if id does not exist" do
|
|
137
|
+
lambda { Registration.find("blah") }.should raise_error(ActiveUrl::RecordNotFound)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mholling-active_url
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew Hollingworth
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-05-
|
|
12
|
+
date: 2009-05-25 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -44,8 +44,11 @@ files:
|
|
|
44
44
|
- lib/active_url/crypto.rb
|
|
45
45
|
- lib/active_url/errors.rb
|
|
46
46
|
- lib/active_url/validations.rb
|
|
47
|
-
- spec/
|
|
47
|
+
- spec/belongs_to_spec.rb
|
|
48
|
+
- spec/crypto_spec.rb
|
|
49
|
+
- spec/instance_spec.rb
|
|
48
50
|
- spec/spec_helper.rb
|
|
51
|
+
- spec/validations_spec.rb
|
|
49
52
|
has_rdoc: false
|
|
50
53
|
homepage: http://github.com/mholling/active_url
|
|
51
54
|
post_install_message:
|
|
@@ -73,5 +76,8 @@ signing_key:
|
|
|
73
76
|
specification_version: 2
|
|
74
77
|
summary: A Rails library for generating secret URLs.
|
|
75
78
|
test_files:
|
|
76
|
-
- spec/
|
|
79
|
+
- spec/belongs_to_spec.rb
|
|
80
|
+
- spec/crypto_spec.rb
|
|
81
|
+
- spec/instance_spec.rb
|
|
77
82
|
- spec/spec_helper.rb
|
|
83
|
+
- spec/validations_spec.rb
|
data/spec/active_url_spec.rb
DELETED
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe ActiveUrl do
|
|
4
|
-
before(:all) do
|
|
5
|
-
ActiveUrl.config.secret = 'secret'
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
context "instance" do
|
|
9
|
-
before(:each) do
|
|
10
|
-
@url = ActiveUrl::Base.new
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it "should have nil id" do
|
|
14
|
-
@url.id.should be_nil
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it "should be a new_record" do
|
|
18
|
-
@url.should be_new_record
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
it "should be saveable" do
|
|
22
|
-
@url.save.should be_true
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
context "after saving" do
|
|
26
|
-
before(:each) do
|
|
27
|
-
@url.save
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it "should have an id" do
|
|
31
|
-
@url.id.should_not be_blank
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
it "should not be a new record" do
|
|
35
|
-
@url.should_not be_new_record
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
context "derived" do
|
|
41
|
-
before(:all) do
|
|
42
|
-
class DerivedClass < ActiveUrl::Base
|
|
43
|
-
attribute :foo, :bar
|
|
44
|
-
attribute :baz, :accessible => true
|
|
45
|
-
attr_accessible :bar
|
|
46
|
-
|
|
47
|
-
attr_accessor :x, :y
|
|
48
|
-
attr_accessible :y
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
context "instance" do
|
|
53
|
-
it "should not mass-assign attributes by default" do
|
|
54
|
-
@url = DerivedClass.new(:foo => "foo")
|
|
55
|
-
@url.foo.should be_nil
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
it "should mass-assign attributes declared as attr_accessible" do
|
|
59
|
-
@url = DerivedClass.new(:bar => "bar")
|
|
60
|
-
@url.bar.should == "bar"
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
it "should mass-assigned attributes with :accessible specified on declaration" do
|
|
64
|
-
@url = DerivedClass.new(:baz => "baz")
|
|
65
|
-
@url.baz.should == "baz"
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
it "should not mass-assign virtual attributes by default" do
|
|
69
|
-
@url = DerivedClass.new(:x => "x")
|
|
70
|
-
@url.x.should be_nil
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
it "should mass-assign its accessible virtual attributes" do
|
|
74
|
-
@url = DerivedClass.new(:y => "y")
|
|
75
|
-
@url.y.should == "y"
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it "should know its mass-assignable attribute names" do
|
|
79
|
-
@url = DerivedClass.new
|
|
80
|
-
[ :bar, :baz, :y ].each { |name| @url.accessible_attributes.should include(name) }
|
|
81
|
-
[ :foo, :x ].each { |name| @url.accessible_attributes.should_not include(name) }
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
it "should know its attribute names" do
|
|
85
|
-
@url = DerivedClass.new
|
|
86
|
-
[ :foo, :bar, :baz ].each { |name| @url.attribute_names.should include(name) }
|
|
87
|
-
[ :x, :y ].each { |name| @url.attribute_names.should_not include(name) }
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
context "equality" do
|
|
91
|
-
before(:all) do
|
|
92
|
-
class OtherClass < DerivedClass
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it "should be based on class and attributes only" do
|
|
97
|
-
@url = DerivedClass.new(:bar => "bar", :baz => "baz")
|
|
98
|
-
@url2 = DerivedClass.new(:bar => "bar", :baz => "baz")
|
|
99
|
-
@url3 = DerivedClass.new(:bar => "BAR", :baz => "baz")
|
|
100
|
-
@url4 = OtherClass.new(:bar => "bar", :baz => "baz")
|
|
101
|
-
@url.should == @url2
|
|
102
|
-
@url.should_not == @url3
|
|
103
|
-
@url.should_not == @url4
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
context "class" do
|
|
109
|
-
it "should know its mass-assignable attribute names" do
|
|
110
|
-
[ :bar, :baz, :y ].each { |name| DerivedClass.accessible_attributes.should include(name) }
|
|
111
|
-
[ :foo, :x ].each { |name| DerivedClass.accessible_attributes.should_not include(name) }
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
it "should know its attribute names" do
|
|
115
|
-
[ :foo, :bar, :baz ].each { |name| DerivedClass.attribute_names.should include(name) }
|
|
116
|
-
[ :x, :y ].each { |name| DerivedClass.attribute_names.should_not include(name) }
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
context "instance with validations" do
|
|
122
|
-
before(:all) do
|
|
123
|
-
class Registration < ActiveUrl::Base
|
|
124
|
-
attribute :name, :email, :password, :age, :accessible => true
|
|
125
|
-
validates_presence_of :name
|
|
126
|
-
validates_format_of :email, :with => /^[\w\.=-]+@[\w\.-]+\.[a-zA-Z]{2,4}$/ix
|
|
127
|
-
validates_length_of :password, :minimum => 8
|
|
128
|
-
validates_numericality_of :age
|
|
129
|
-
after_save :send_registration_email
|
|
130
|
-
|
|
131
|
-
def send_registration_email
|
|
132
|
-
@sent = true
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
context "when invalid" do
|
|
138
|
-
before(:each) do
|
|
139
|
-
@registration = Registration.new(:email => "user @ example . com", :password => "short", :age => "ten")
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
it "should not validate" do
|
|
143
|
-
@registration.should_not be_valid
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
it "should not save" do
|
|
147
|
-
@registration.save.should_not be_true
|
|
148
|
-
@registration.id.should be_nil
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
it "should raise ActiveUrl::InvalidRecord when saved with bang" do
|
|
152
|
-
lambda { @registration.save! }.should raise_error(ActiveUrl::RecordInvalid)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
context "and saved" do
|
|
156
|
-
before(:each) do
|
|
157
|
-
@registration.save
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
it "should have errors" do
|
|
161
|
-
@registration.errors.should_not be_empty
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
it "should validate presence of an attribute" do
|
|
165
|
-
@registration.errors[:name].should_not be_blank
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
it "should validate format of an attribute" do
|
|
169
|
-
@registration.errors[:email].should_not be_blank
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
it "should validate length of an attribute" do
|
|
173
|
-
@registration.errors[:password].should_not be_nil
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
it "should validate numericality of an attribute" do
|
|
177
|
-
@registration.errors[:age].should_not be_nil
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
it "should not execute any after_save callbacks" do
|
|
181
|
-
@registration.instance_variables.should_not include("@sent")
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
context "when valid" do
|
|
187
|
-
before(:each) do
|
|
188
|
-
@registration = Registration.new(:name => "John Doe", :email => "user@example.com", :password => "password", :age => "10")
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
it "should validate" do
|
|
192
|
-
@registration.should be_valid
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
context "and saved" do
|
|
196
|
-
before(:each) do
|
|
197
|
-
@registration.save
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
it "should have an id" do
|
|
201
|
-
@registration.id.should_not be_blank
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
it "should have a param equal to its id" do
|
|
205
|
-
@registration.id.should == @registration.to_param
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
it "should execute any after_save callbacks" do
|
|
209
|
-
@registration.instance_variables.should include("@sent")
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
context "and re-found by its class" do
|
|
213
|
-
before(:each) do
|
|
214
|
-
@found = Registration.find(@registration.id)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
it "should exist" do
|
|
218
|
-
@found.should_not be_nil
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
it "should have the same id" do
|
|
222
|
-
@found.id.should == @registration.id
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
it "should have the same attributes" do
|
|
226
|
-
@found.attributes.should == @registration.attributes
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
it "should be valid" do
|
|
230
|
-
@found.should be_valid
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
context "and subsequently made invalid" do
|
|
235
|
-
before(:each) do
|
|
236
|
-
@registration.password = "short"
|
|
237
|
-
@registration.stub!(:valid?).and_return(true)
|
|
238
|
-
@registration.save
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
it "should not be found by its class" do
|
|
242
|
-
@registration.id.should_not be_blank
|
|
243
|
-
lambda { Registration.find(@registration.id) }.should raise_error(ActiveUrl::RecordNotFound)
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
it "should raise ActiveUrl::RecordNotFound if id does not exist" do
|
|
250
|
-
lambda { Registration.find("blah") }.should raise_error(ActiveUrl::RecordNotFound)
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
context "instance with belongs_to association" do
|
|
255
|
-
before(:all) do
|
|
256
|
-
# a simple pretend-ActiveRecord model for testing belongs_to without setting up a db:
|
|
257
|
-
class ::User < ActiveRecord::Base
|
|
258
|
-
def self.columns() @columns ||= []; end
|
|
259
|
-
def self.column(name, sql_type = nil, default = nil, null = true)
|
|
260
|
-
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
class Secret < ActiveUrl::Base
|
|
265
|
-
belongs_to :user
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
before(:each) do
|
|
270
|
-
@url = Secret.new
|
|
271
|
-
@user = User.new
|
|
272
|
-
@user.stub!(:id).and_return(1)
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
it "should raise ArgumentError if the association name is not an ActiveRecord class" do
|
|
276
|
-
lambda { Secret.belongs_to :foo }.should raise_error(ArgumentError)
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
it "should respond to association_id, association_id=, association & association=" do
|
|
280
|
-
@url.attribute_names.should include(:user_id)
|
|
281
|
-
@url.should respond_to(:user)
|
|
282
|
-
@url.should respond_to(:user=)
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
it "should have nil association if association or association_id not set" do
|
|
286
|
-
@url.user.should be_nil
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
it "should not allow mass assignment of association_id" do
|
|
290
|
-
@url = Secret.new(:user_id => @user.id)
|
|
291
|
-
@url.user_id.should be_nil
|
|
292
|
-
@url.user.should be_nil
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
it "should not allow mass assignment of association" do
|
|
296
|
-
@url = Secret.new(:user => @user)
|
|
297
|
-
@url.user_id.should be_nil
|
|
298
|
-
@url.user.should be_nil
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
it "should be able to have its association set to nil" do
|
|
302
|
-
@url.user_id = @user.id
|
|
303
|
-
@url.user = nil
|
|
304
|
-
@url.user_id.should be_nil
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
it "should raise ArgumentError if association is set to wrong type" do
|
|
308
|
-
lambda { @url.user = Object.new }.should raise_error(TypeError)
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
it "should find its association_id if association is set" do
|
|
312
|
-
@url.user = @user
|
|
313
|
-
@url.user_id.should == @user.id
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
it "should find its association if association_id is set" do
|
|
317
|
-
User.should_receive(:find).with(@user.id).and_return(@user)
|
|
318
|
-
@url.user_id = @user.id
|
|
319
|
-
@url.user.should == @user
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
it "should return nil association if association_id is unknown" do
|
|
323
|
-
User.should_receive(:find).and_raise(ActiveRecord::RecordNotFound)
|
|
324
|
-
@url.user_id = 10
|
|
325
|
-
@url.user.should be_nil
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
it "should know its association when found by id" do
|
|
329
|
-
User.should_receive(:find).with(@user.id).and_return(@user)
|
|
330
|
-
@url.user_id = @user.id
|
|
331
|
-
@url.save
|
|
332
|
-
@found = Secret.find(@url.id)
|
|
333
|
-
@found.user.should == @user
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
describe ActiveUrl::Crypto do
|
|
340
|
-
it "should raise ArgumentError if no secret is set" do
|
|
341
|
-
ActiveUrl.config.secret = nil
|
|
342
|
-
lambda { ActiveUrl::Crypto.encrypt("clear") }.should raise_error(ArgumentError)
|
|
343
|
-
ActiveUrl.config.secret = 'secret'
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
it "should decode what it encodes" do
|
|
347
|
-
ActiveUrl::Crypto.decrypt(ActiveUrl::Crypto.encrypt("clear")).should == "clear"
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
it "should always yield URL-safe output characters" do
|
|
351
|
-
url_safe = /^[\w\-]*$/
|
|
352
|
-
(1..20).each do |n|
|
|
353
|
-
clear = (0...8).inject("") { |string, n| string << rand(255).chr } # random string
|
|
354
|
-
cipher = ActiveUrl::Crypto.encrypt(clear)
|
|
355
|
-
cipher.should =~ url_safe
|
|
356
|
-
end
|
|
357
|
-
end
|
|
358
|
-
end
|