active_url 0.1.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/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.textile +454 -0
- data/Rakefile +57 -0
- data/VERSION.yml +4 -0
- data/active_url.gemspec +72 -0
- data/lib/active_url.rb +20 -0
- data/lib/active_url/base.rb +103 -0
- data/lib/active_url/belongs_to.rb +32 -0
- data/lib/active_url/callbacks.rb +17 -0
- data/lib/active_url/configuration.rb +5 -0
- data/lib/active_url/crypto.rb +34 -0
- data/lib/active_url/errors.rb +4 -0
- data/lib/active_url/validations.rb +57 -0
- data/rails/init.rb +1 -0
- data/spec/belongs_to_spec.rb +94 -0
- data/spec/callbacks_spec.rb +45 -0
- data/spec/crypto_spec.rb +27 -0
- data/spec/instance_spec.rb +138 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/validations_spec.rb +131 -0
- metadata +91 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require 'jeweler'
|
|
7
|
+
Jeweler::Tasks.new do |gem|
|
|
8
|
+
gem.name = "active_url"
|
|
9
|
+
gem.summary = %Q{A Rails library for generating secret URLs.}
|
|
10
|
+
gem.description = <<-EOF
|
|
11
|
+
ActiveUrl enables the storing of a model in an encrypted URL. It facilitates implementation
|
|
12
|
+
of secret URLs for user (e.g. feed URLs) that can be accessed without logging in, and URLs
|
|
13
|
+
for confirming the email address of a new user.
|
|
14
|
+
EOF
|
|
15
|
+
gem.email = "mdholling@gmail.com"
|
|
16
|
+
gem.homepage = "http://github.com/mholling/active_url"
|
|
17
|
+
gem.authors = ["Matthew Hollingworth"]
|
|
18
|
+
gem.add_dependency 'activerecord'
|
|
19
|
+
gem.has_rdoc = false
|
|
20
|
+
|
|
21
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
22
|
+
end
|
|
23
|
+
Jeweler::GemcutterTasks.new
|
|
24
|
+
rescue LoadError
|
|
25
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
require 'spec/rake/spectask'
|
|
29
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
|
31
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
|
35
|
+
spec.libs << 'lib' << 'spec'
|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
|
37
|
+
spec.rcov = true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
task :default => :spec
|
|
42
|
+
|
|
43
|
+
require 'rake/rdoctask'
|
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
|
45
|
+
if File.exist?('VERSION.yml')
|
|
46
|
+
config = YAML.load(File.read('VERSION.yml'))
|
|
47
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
|
48
|
+
else
|
|
49
|
+
version = ""
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
53
|
+
rdoc.title = "active_url #{version}"
|
|
54
|
+
rdoc.rdoc_files.include('README*')
|
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
56
|
+
end
|
|
57
|
+
|
data/VERSION.yml
ADDED
data/active_url.gemspec
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Generated by jeweler
|
|
2
|
+
# DO NOT EDIT THIS FILE
|
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
|
4
|
+
# -*- encoding: utf-8 -*-
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = %q{active_url}
|
|
8
|
+
s.version = "0.1.4"
|
|
9
|
+
|
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
|
+
s.authors = ["Matthew Hollingworth"]
|
|
12
|
+
s.date = %q{2009-10-10}
|
|
13
|
+
s.description = %q{ ActiveUrl enables the storing of a model in an encrypted URL. It facilitates implementation
|
|
14
|
+
of secret URLs for user (e.g. feed URLs) that can be accessed without logging in, and URLs
|
|
15
|
+
for confirming the email address of a new user.
|
|
16
|
+
}
|
|
17
|
+
s.email = %q{mdholling@gmail.com}
|
|
18
|
+
s.extra_rdoc_files = [
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"README.textile"
|
|
21
|
+
]
|
|
22
|
+
s.files = [
|
|
23
|
+
".document",
|
|
24
|
+
".gitignore",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"README.textile",
|
|
27
|
+
"Rakefile",
|
|
28
|
+
"VERSION.yml",
|
|
29
|
+
"active_url.gemspec",
|
|
30
|
+
"lib/active_url.rb",
|
|
31
|
+
"lib/active_url/base.rb",
|
|
32
|
+
"lib/active_url/belongs_to.rb",
|
|
33
|
+
"lib/active_url/callbacks.rb",
|
|
34
|
+
"lib/active_url/configuration.rb",
|
|
35
|
+
"lib/active_url/crypto.rb",
|
|
36
|
+
"lib/active_url/errors.rb",
|
|
37
|
+
"lib/active_url/validations.rb",
|
|
38
|
+
"rails/init.rb",
|
|
39
|
+
"spec/belongs_to_spec.rb",
|
|
40
|
+
"spec/callbacks_spec.rb",
|
|
41
|
+
"spec/crypto_spec.rb",
|
|
42
|
+
"spec/instance_spec.rb",
|
|
43
|
+
"spec/spec_helper.rb",
|
|
44
|
+
"spec/validations_spec.rb"
|
|
45
|
+
]
|
|
46
|
+
s.homepage = %q{http://github.com/mholling/active_url}
|
|
47
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
|
48
|
+
s.require_paths = ["lib"]
|
|
49
|
+
s.rubygems_version = %q{1.3.5}
|
|
50
|
+
s.summary = %q{A Rails library for generating secret URLs.}
|
|
51
|
+
s.test_files = [
|
|
52
|
+
"spec/belongs_to_spec.rb",
|
|
53
|
+
"spec/callbacks_spec.rb",
|
|
54
|
+
"spec/crypto_spec.rb",
|
|
55
|
+
"spec/instance_spec.rb",
|
|
56
|
+
"spec/spec_helper.rb",
|
|
57
|
+
"spec/validations_spec.rb"
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
if s.respond_to? :specification_version then
|
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
62
|
+
s.specification_version = 3
|
|
63
|
+
|
|
64
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
|
65
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 0"])
|
|
66
|
+
else
|
|
67
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/active_url.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'active_record'
|
|
3
|
+
ActiveRecord::Base # hack to get ActiveRecord::Validations to load..?
|
|
4
|
+
|
|
5
|
+
require 'active_url/errors'
|
|
6
|
+
require 'active_url/configuration'
|
|
7
|
+
require 'active_url/crypto'
|
|
8
|
+
require 'active_url/belongs_to'
|
|
9
|
+
require 'active_url/validations'
|
|
10
|
+
require 'active_url/callbacks'
|
|
11
|
+
require 'active_url/base'
|
|
12
|
+
|
|
13
|
+
# module ActiveUrl
|
|
14
|
+
# autoload :Base, 'active_url/base'
|
|
15
|
+
# autoload :Configuration, 'active_url/configuration'
|
|
16
|
+
# autoload :Crypto, 'active_url/crypto'
|
|
17
|
+
# autoload :BelongsTo, 'active_url/belongs_to'
|
|
18
|
+
# autoload :Validations, 'active_url/validations'
|
|
19
|
+
# autoload :Callbacks, 'active_url/callbacks'
|
|
20
|
+
# end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module ActiveUrl
|
|
2
|
+
class RecordNotFound < ActiveUrlError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class Base
|
|
6
|
+
class_inheritable_reader :attribute_names
|
|
7
|
+
class_inheritable_reader :accessible_attributes
|
|
8
|
+
|
|
9
|
+
def self.attribute(*attribute_names)
|
|
10
|
+
options = attribute_names.extract_options!
|
|
11
|
+
attribute_names.map(&:to_sym).each { |attribute_name| add_attribute(attribute_name, options) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.attr_accessible(*attribute_names)
|
|
15
|
+
self.accessible_attributes += attribute_names.map(&:to_sym)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :id
|
|
19
|
+
|
|
20
|
+
def initialize(attributes = nil)
|
|
21
|
+
attributes ||= {}
|
|
22
|
+
self.attributes = attributes
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def attributes=(attributes)
|
|
26
|
+
attributes.symbolize_keys.select do |key, value|
|
|
27
|
+
self.class.accessible_attributes.include? key
|
|
28
|
+
end.map do |key, value|
|
|
29
|
+
[ "#{key}=", value ]
|
|
30
|
+
end.each do |setter, value|
|
|
31
|
+
send setter, value if respond_to? setter
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def attributes
|
|
36
|
+
attribute_names.inject({}) do |hash, name|
|
|
37
|
+
hash.merge(name => send(name))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create
|
|
42
|
+
serialized = [ self.class.to_s, attributes ].to_yaml
|
|
43
|
+
@id = Crypto.encrypt(serialized)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def save
|
|
47
|
+
!create.blank?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def save!
|
|
51
|
+
save
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.find(id)
|
|
55
|
+
raise RecordNotFound unless id.is_a?(String) && !id.blank?
|
|
56
|
+
serialized = begin
|
|
57
|
+
Crypto.decrypt(id)
|
|
58
|
+
rescue Crypto::CipherError
|
|
59
|
+
raise RecordNotFound
|
|
60
|
+
end
|
|
61
|
+
type, attributes = YAML.load(serialized)
|
|
62
|
+
raise RecordNotFound unless type == self.to_s && attributes.is_a?(Hash)
|
|
63
|
+
active_url = new
|
|
64
|
+
attributes.each { |key, value| active_url.send "#{key}=", value }
|
|
65
|
+
active_url.create
|
|
66
|
+
active_url
|
|
67
|
+
rescue RecordNotFound
|
|
68
|
+
raise RecordNotFound.new("Couldn't find #{self.name} with id=#{id}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def to_param
|
|
72
|
+
@id.to_s
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def new_record?
|
|
76
|
+
@id.nil?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def ==(other)
|
|
80
|
+
attributes == other.attributes && self.class == other.class
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
class_inheritable_writer :attribute_names
|
|
86
|
+
class_inheritable_writer :accessible_attributes
|
|
87
|
+
self.attribute_names = Set.new
|
|
88
|
+
self.accessible_attributes = Set.new
|
|
89
|
+
|
|
90
|
+
def self.add_attribute(attribute_name, options)
|
|
91
|
+
self.attribute_names << attribute_name
|
|
92
|
+
self.accessible_attributes << attribute_name if options[:accessible]
|
|
93
|
+
public
|
|
94
|
+
attr_accessor attribute_name
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
Base.class_eval do
|
|
99
|
+
extend BelongsTo
|
|
100
|
+
include Validations
|
|
101
|
+
include Callbacks
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module ActiveUrl
|
|
2
|
+
module BelongsTo
|
|
3
|
+
def belongs_to(object_name)
|
|
4
|
+
begin
|
|
5
|
+
object_name.to_s.classify.constantize
|
|
6
|
+
|
|
7
|
+
attribute_name = "#{object_name}_id"
|
|
8
|
+
attribute attribute_name
|
|
9
|
+
|
|
10
|
+
define_method object_name do
|
|
11
|
+
begin
|
|
12
|
+
object_name.to_s.classify.constantize.find(send(attribute_name))
|
|
13
|
+
rescue ActiveRecord::RecordNotFound
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
define_method "#{object_name}=" do |object|
|
|
19
|
+
if object.nil?
|
|
20
|
+
self.send "#{object_name}_id=", nil
|
|
21
|
+
elsif object.is_a?(object_name.to_s.classify.constantize)
|
|
22
|
+
self.send "#{object_name}_id=", object.id
|
|
23
|
+
else
|
|
24
|
+
raise TypeError.new("object is not of type #{object_name.to_s.classify}")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
rescue NameError
|
|
28
|
+
raise ArgumentError.new("#{object_name.to_s.classify} is not an ActiveRecord class.")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ActiveUrl
|
|
2
|
+
module Callbacks
|
|
3
|
+
def save_with_callbacks
|
|
4
|
+
returning(save_without_callbacks) do |result|
|
|
5
|
+
run_callbacks(:after_save) if result
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.class_eval do
|
|
11
|
+
include ActiveSupport::Callbacks # Already included by ActiveRecord.
|
|
12
|
+
alias_method_chain :save, :callbacks
|
|
13
|
+
define_callbacks :after_save
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'openssl'
|
|
2
|
+
require 'digest/sha2'
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module ActiveUrl
|
|
6
|
+
module Crypto
|
|
7
|
+
CipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
|
|
8
|
+
|
|
9
|
+
PADDING = { 2 => "==", 3 => "=" }
|
|
10
|
+
|
|
11
|
+
def self.encrypt(clear)
|
|
12
|
+
crypto = start(:encrypt)
|
|
13
|
+
cipher = crypto.update(clear)
|
|
14
|
+
cipher << crypto.final
|
|
15
|
+
Base64.encode64(cipher).gsub(/[\s=]+/, "").gsub("+", "-").gsub("/", "_")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.decrypt(b64)
|
|
19
|
+
cipher = Base64.decode64("#{b64.gsub("-", "+").gsub("_", "/")}#{PADDING[b64.length % 4]}")
|
|
20
|
+
crypto = start(:decrypt)
|
|
21
|
+
clear = crypto.update(cipher)
|
|
22
|
+
clear << crypto.final
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def self.start(mode)
|
|
28
|
+
raise ::ArgumentError.new("Set a secret key using ActiveUrl::Config.secret = 'your-secret'") if ActiveUrl::Config.secret.blank?
|
|
29
|
+
crypto = OpenSSL::Cipher::Cipher.new('aes-256-ecb').send(mode)
|
|
30
|
+
crypto.key = Digest::SHA256.hexdigest(ActiveUrl::Config.secret)
|
|
31
|
+
return crypto
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module ActiveUrl
|
|
2
|
+
class RecordInvalid < ActiveUrlError
|
|
3
|
+
attr_reader :record
|
|
4
|
+
def initialize(record)
|
|
5
|
+
@record = record
|
|
6
|
+
super("Validation failed: #{@record.errors.full_messages.join(", ")}")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Validations
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def self_and_descendants_from_active_record
|
|
13
|
+
[self]
|
|
14
|
+
end
|
|
15
|
+
alias_method :self_and_descendents_from_active_record, :self_and_descendants_from_active_record
|
|
16
|
+
|
|
17
|
+
def human_name()
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def human_attribute_name(name, options = {})
|
|
21
|
+
name.to_s.humanize
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def find_with_validation(id)
|
|
25
|
+
active_url = find_without_validation(id)
|
|
26
|
+
raise ActiveUrl::RecordNotFound unless active_url.valid?
|
|
27
|
+
active_url
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def add_attribute_with_validation(attribute_name, options)
|
|
33
|
+
add_attribute_without_validation(attribute_name, options)
|
|
34
|
+
alias_method "#{attribute_name}_before_type_cast", attribute_name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def save_with_active_url_exception!
|
|
40
|
+
save_without_active_url_exception!
|
|
41
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
42
|
+
raise ActiveUrl::RecordInvalid.new(e.record)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.included(base)
|
|
46
|
+
base.class_eval do
|
|
47
|
+
extend ClassMethods
|
|
48
|
+
include ActiveRecord::Validations
|
|
49
|
+
alias_method_chain :save!, :active_url_exception
|
|
50
|
+
class << self
|
|
51
|
+
alias_method_chain :find, :validation
|
|
52
|
+
alias_method_chain :add_attribute, :validation
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/rails/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'active_url'
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
after(:all) do
|
|
24
|
+
Object.send(:remove_const, "Secret")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
before(:each) do
|
|
28
|
+
@url = Secret.new
|
|
29
|
+
@user = User.new
|
|
30
|
+
@user.stub!(:id).and_return(1)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should raise ArgumentError if the association name is not an ActiveRecord class" do
|
|
34
|
+
lambda { Secret.belongs_to :foo }.should raise_error(ArgumentError)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "should respond to association_id, association_id=, association & association=" do
|
|
38
|
+
@url.attribute_names.should include(:user_id)
|
|
39
|
+
@url.should respond_to(:user)
|
|
40
|
+
@url.should respond_to(:user=)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "should have nil association if association or association_id not set" do
|
|
44
|
+
@url.user.should be_nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "should not allow mass assignment of association_id" do
|
|
48
|
+
@url = Secret.new(:user_id => @user.id)
|
|
49
|
+
@url.user_id.should be_nil
|
|
50
|
+
@url.user.should be_nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "should not allow mass assignment of association" do
|
|
54
|
+
@url = Secret.new(:user => @user)
|
|
55
|
+
@url.user_id.should be_nil
|
|
56
|
+
@url.user.should be_nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "should be able to have its association set to nil" do
|
|
60
|
+
@url.user_id = @user.id
|
|
61
|
+
@url.user = nil
|
|
62
|
+
@url.user_id.should be_nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should raise ArgumentError if association is set to wrong type" do
|
|
66
|
+
lambda { @url.user = Object.new }.should raise_error(TypeError)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "should find its association_id if association is set" do
|
|
70
|
+
@url.user = @user
|
|
71
|
+
@url.user_id.should == @user.id
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "should find its association if association_id is set" do
|
|
75
|
+
User.should_receive(:find).with(@user.id).and_return(@user)
|
|
76
|
+
@url.user_id = @user.id
|
|
77
|
+
@url.user.should == @user
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "should return nil association if association_id is unknown" do
|
|
81
|
+
User.should_receive(:find).and_raise(ActiveRecord::RecordNotFound)
|
|
82
|
+
@url.user_id = 10
|
|
83
|
+
@url.user.should be_nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "should know its association when found by id" do
|
|
87
|
+
User.should_receive(:find).with(@user.id).and_return(@user)
|
|
88
|
+
@url.user_id = @user.id
|
|
89
|
+
@url.save
|
|
90
|
+
@found = Secret.find(@url.id)
|
|
91
|
+
@found.user.should == @user
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|