couchrest_model-radiant 1.0.0
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/LICENSE +176 -0
- data/README.md +19 -0
- data/Rakefile +74 -0
- data/THANKS.md +21 -0
- data/history.txt +207 -0
- data/lib/couchrest/model.rb +10 -0
- data/lib/couchrest/model/associations.rb +223 -0
- data/lib/couchrest/model/base.rb +111 -0
- data/lib/couchrest/model/callbacks.rb +27 -0
- data/lib/couchrest/model/casted_array.rb +39 -0
- data/lib/couchrest/model/casted_model.rb +68 -0
- data/lib/couchrest/model/class_proxy.rb +122 -0
- data/lib/couchrest/model/collection.rb +263 -0
- data/lib/couchrest/model/configuration.rb +51 -0
- data/lib/couchrest/model/design_doc.rb +123 -0
- data/lib/couchrest/model/document_queries.rb +83 -0
- data/lib/couchrest/model/errors.rb +23 -0
- data/lib/couchrest/model/extended_attachments.rb +77 -0
- data/lib/couchrest/model/persistence.rb +155 -0
- data/lib/couchrest/model/properties.rb +208 -0
- data/lib/couchrest/model/property.rb +97 -0
- data/lib/couchrest/model/property_protection.rb +71 -0
- data/lib/couchrest/model/support/couchrest.rb +19 -0
- data/lib/couchrest/model/support/hash.rb +9 -0
- data/lib/couchrest/model/typecast.rb +175 -0
- data/lib/couchrest/model/validations.rb +68 -0
- data/lib/couchrest/model/validations/casted_model.rb +14 -0
- data/lib/couchrest/model/validations/locale/en.yml +5 -0
- data/lib/couchrest/model/validations/uniqueness.rb +44 -0
- data/lib/couchrest/model/views.rb +160 -0
- data/lib/couchrest/railtie.rb +12 -0
- data/lib/couchrest_model.rb +62 -0
- data/lib/rails/generators/couchrest_model.rb +16 -0
- data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
- data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
- data/spec/couchrest/assocations_spec.rb +196 -0
- data/spec/couchrest/attachment_spec.rb +176 -0
- data/spec/couchrest/base_spec.rb +463 -0
- data/spec/couchrest/casted_model_spec.rb +438 -0
- data/spec/couchrest/casted_spec.rb +75 -0
- data/spec/couchrest/class_proxy_spec.rb +132 -0
- data/spec/couchrest/configuration_spec.rb +78 -0
- data/spec/couchrest/inherited_spec.rb +40 -0
- data/spec/couchrest/persistence_spec.rb +415 -0
- data/spec/couchrest/property_protection_spec.rb +192 -0
- data/spec/couchrest/property_spec.rb +871 -0
- data/spec/couchrest/subclass_spec.rb +99 -0
- data/spec/couchrest/validations.rb +85 -0
- data/spec/couchrest/view_spec.rb +463 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/base.rb +139 -0
- data/spec/fixtures/more/article.rb +35 -0
- data/spec/fixtures/more/card.rb +17 -0
- data/spec/fixtures/more/cat.rb +19 -0
- data/spec/fixtures/more/client.rb +6 -0
- data/spec/fixtures/more/course.rb +25 -0
- data/spec/fixtures/more/event.rb +8 -0
- data/spec/fixtures/more/invoice.rb +14 -0
- data/spec/fixtures/more/person.rb +9 -0
- data/spec/fixtures/more/question.rb +7 -0
- data/spec/fixtures/more/sale_entry.rb +9 -0
- data/spec/fixtures/more/sale_invoice.rb +13 -0
- data/spec/fixtures/more/service.rb +10 -0
- data/spec/fixtures/more/user.rb +22 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec_helper.rb +48 -0
- metadata +263 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module CouchRest
|
4
|
+
module Model
|
5
|
+
module Validations
|
6
|
+
|
7
|
+
# Validates if a field is unique
|
8
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
9
|
+
|
10
|
+
# Ensure we have a class available so we can check for a usable view
|
11
|
+
# or add one if necessary.
|
12
|
+
def setup(klass)
|
13
|
+
@klass = klass
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def validate_each(document, attribute, value)
|
18
|
+
view_name = options[:view].nil? ? "by_#{attribute}" : options[:view]
|
19
|
+
# Determine the base of the search
|
20
|
+
base = options[:proxy].nil? ? @klass : document.instance_eval(options[:proxy])
|
21
|
+
|
22
|
+
if base.respond_to?(:has_view?) && !base.has_view?(view_name)
|
23
|
+
raise "View #{document.class.name}.#{options[:view]} does not exist!" unless options[:view].nil?
|
24
|
+
@klass.view_by attribute
|
25
|
+
end
|
26
|
+
|
27
|
+
docs = base.view(view_name, :key => value, :limit => 2, :include_docs => false)['rows']
|
28
|
+
return if docs.empty?
|
29
|
+
|
30
|
+
unless document.new?
|
31
|
+
return if docs.find{|doc| doc['id'] == document.id}
|
32
|
+
end
|
33
|
+
|
34
|
+
if docs.length > 0
|
35
|
+
document.errors.add(attribute, :taken, :default => options[:message], :value => value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
module Views
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
# Define a CouchDB view. The name of the view will be the concatenation
|
8
|
+
# of <tt>by</tt> and the keys joined by <tt>_and_</tt>
|
9
|
+
#
|
10
|
+
# ==== Example views:
|
11
|
+
#
|
12
|
+
# class Post
|
13
|
+
# # view with default options
|
14
|
+
# # query with Post.by_date
|
15
|
+
# view_by :date, :descending => true
|
16
|
+
#
|
17
|
+
# # view with compound sort-keys
|
18
|
+
# # query with Post.by_user_id_and_date
|
19
|
+
# view_by :user_id, :date
|
20
|
+
#
|
21
|
+
# # view with custom map/reduce functions
|
22
|
+
# # query with Post.by_tags :reduce => true
|
23
|
+
# view_by :tags,
|
24
|
+
# :map =>
|
25
|
+
# "function(doc) {
|
26
|
+
# if (doc['model'] == 'Post' && doc.tags) {
|
27
|
+
# doc.tags.forEach(function(tag){
|
28
|
+
# emit(doc.tag, 1);
|
29
|
+
# });
|
30
|
+
# }
|
31
|
+
# }",
|
32
|
+
# :reduce =>
|
33
|
+
# "function(keys, values, rereduce) {
|
34
|
+
# return sum(values);
|
35
|
+
# }"
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# <tt>view_by :date</tt> will create a view defined by this Javascript
|
39
|
+
# function:
|
40
|
+
#
|
41
|
+
# function(doc) {
|
42
|
+
# if (doc['model'] == 'Post' && doc.date) {
|
43
|
+
# emit(doc.date, null);
|
44
|
+
# }
|
45
|
+
# }
|
46
|
+
#
|
47
|
+
# It can be queried by calling <tt>Post.by_date</tt> which accepts all
|
48
|
+
# valid options for CouchRest::Database#view. In addition, calling with
|
49
|
+
# the <tt>:raw => true</tt> option will return the view rows
|
50
|
+
# themselves. By default <tt>Post.by_date</tt> will return the
|
51
|
+
# documents included in the generated view.
|
52
|
+
#
|
53
|
+
# Calling with :database => [instance of CouchRest::Database] will
|
54
|
+
# send the query to a specific database, otherwise it will go to
|
55
|
+
# the model's default database (use_database)
|
56
|
+
#
|
57
|
+
# CouchRest::Database#view options can be applied at view definition
|
58
|
+
# time as defaults, and they will be curried and used at view query
|
59
|
+
# time. Or they can be overridden at query time.
|
60
|
+
#
|
61
|
+
# Custom views can be queried with <tt>:reduce => true</tt> to return
|
62
|
+
# reduce results. The default for custom views is to query with
|
63
|
+
# <tt>:reduce => false</tt>.
|
64
|
+
#
|
65
|
+
# Views are generated (on a per-model basis) lazily on first-access.
|
66
|
+
# This means that if you are deploying changes to a view, the views for
|
67
|
+
# that model won't be available until generation is complete. This can
|
68
|
+
# take some time with large databases. Strategies are in the works.
|
69
|
+
#
|
70
|
+
# To understand the capabilities of this view system more completely,
|
71
|
+
# it is recommended that you read the RSpec file at
|
72
|
+
# <tt>spec/couchrest/more/extended_doc_spec.rb</tt>.
|
73
|
+
|
74
|
+
def view_by(*keys)
|
75
|
+
opts = keys.pop if keys.last.is_a?(Hash)
|
76
|
+
opts ||= {}
|
77
|
+
ducktype = opts.delete(:ducktype)
|
78
|
+
unless ducktype || opts[:map]
|
79
|
+
opts[:guards] ||= []
|
80
|
+
opts[:guards].push "(doc['#{model_type_key}'] == '#{self.to_s}')"
|
81
|
+
end
|
82
|
+
keys.push opts
|
83
|
+
design_doc.view_by(*keys)
|
84
|
+
req_design_doc_refresh
|
85
|
+
end
|
86
|
+
|
87
|
+
# returns stored defaults if there is a view named this in the design doc
|
88
|
+
def has_view?(view)
|
89
|
+
view = view.to_s
|
90
|
+
design_doc && design_doc['views'] && design_doc['views'][view]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Dispatches to any named view.
|
94
|
+
def view(name, query={}, &block)
|
95
|
+
query = query.dup # Modifications made on copy!
|
96
|
+
db = query.delete(:database) || database
|
97
|
+
refresh_design_doc(db)
|
98
|
+
query[:raw] = true if query[:reduce]
|
99
|
+
raw = query.delete(:raw)
|
100
|
+
fetch_view_with_docs(db, name, query, raw, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Find the first entry in the view. If the second parameter is a string
|
104
|
+
# it will be used as the key for the request, for example:
|
105
|
+
#
|
106
|
+
# Course.first_from_view('by_teacher', 'Fred')
|
107
|
+
#
|
108
|
+
# More advanced requests can be performed by providing a hash:
|
109
|
+
#
|
110
|
+
# Course.first_from_view('by_teacher', :startkey => 'bbb', :endkey => 'eee')
|
111
|
+
#
|
112
|
+
def first_from_view(name, *args)
|
113
|
+
query = {:limit => 1}
|
114
|
+
case args.first
|
115
|
+
when String, Array
|
116
|
+
query.update(args[1]) unless args[1].nil?
|
117
|
+
query[:key] = args.first
|
118
|
+
when Hash
|
119
|
+
query.update(args.first)
|
120
|
+
end
|
121
|
+
view(name, query).first
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def fetch_view_with_docs(db, name, opts, raw=false, &block)
|
127
|
+
if raw || (opts.has_key?(:include_docs) && opts[:include_docs] == false)
|
128
|
+
fetch_view(db, name, opts, &block)
|
129
|
+
else
|
130
|
+
if block.nil?
|
131
|
+
collection_proxy_for(design_doc, name, opts.merge({:database => db, :include_docs => true}))
|
132
|
+
else
|
133
|
+
view = fetch_view db, name, opts.merge({:include_docs => true}), &block
|
134
|
+
view['rows'].collect{|r|create_from_database(r['doc'])} if view['rows']
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def fetch_view(db, view_name, opts, &block)
|
140
|
+
raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless db
|
141
|
+
retryable = true
|
142
|
+
begin
|
143
|
+
design_doc.view_on(db, view_name, opts, &block)
|
144
|
+
# the design doc may not have been saved yet on this database
|
145
|
+
rescue RestClient::ResourceNotFound => e
|
146
|
+
if retryable
|
147
|
+
save_design_doc(db)
|
148
|
+
retryable = false
|
149
|
+
retry
|
150
|
+
else
|
151
|
+
raise e
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end # module ClassMethods
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "rails"
|
2
|
+
require "active_model/railtie"
|
3
|
+
|
4
|
+
module CouchrestModel
|
5
|
+
# = Active Record Railtie
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
config.generators.orm :couchrest_model
|
8
|
+
config.generators.test_framework :test_unit, :fixture => false
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
gem 'couchrest', ">= 1.0.0"
|
2
|
+
require 'couchrest'
|
3
|
+
|
4
|
+
gem "tzinfo", ">= 0.3.22"
|
5
|
+
|
6
|
+
gem 'railties', ">= 3.0.0.rc"
|
7
|
+
gem "activesupport", ">= 3.0.0.rc"
|
8
|
+
|
9
|
+
require 'active_support/core_ext'
|
10
|
+
require 'active_support/json'
|
11
|
+
|
12
|
+
gem "activemodel", ">= 3.0.0.rc"
|
13
|
+
require 'active_model'
|
14
|
+
require "active_model/callbacks"
|
15
|
+
require "active_model/conversion"
|
16
|
+
require "active_model/deprecated_error_methods"
|
17
|
+
require "active_model/errors"
|
18
|
+
require "active_model/naming"
|
19
|
+
require "active_model/serialization"
|
20
|
+
require "active_model/translation"
|
21
|
+
require "active_model/validator"
|
22
|
+
require "active_model/validations"
|
23
|
+
|
24
|
+
gem "mime-types", ">= 1.15"
|
25
|
+
require 'mime/types'
|
26
|
+
require "enumerator"
|
27
|
+
require "time"
|
28
|
+
require 'digest/md5'
|
29
|
+
|
30
|
+
require 'bigdecimal' # used in typecast
|
31
|
+
require 'bigdecimal/util' # used in typecast
|
32
|
+
|
33
|
+
require 'couchrest/model'
|
34
|
+
require 'couchrest/model/errors'
|
35
|
+
require "couchrest/model/persistence"
|
36
|
+
require "couchrest/model/typecast"
|
37
|
+
require "couchrest/model/property"
|
38
|
+
require "couchrest/model/property_protection"
|
39
|
+
require "couchrest/model/casted_array"
|
40
|
+
require "couchrest/model/properties"
|
41
|
+
require "couchrest/model/validations"
|
42
|
+
require "couchrest/model/callbacks"
|
43
|
+
require "couchrest/model/document_queries"
|
44
|
+
require "couchrest/model/views"
|
45
|
+
require "couchrest/model/design_doc"
|
46
|
+
require "couchrest/model/extended_attachments"
|
47
|
+
require "couchrest/model/class_proxy"
|
48
|
+
require "couchrest/model/collection"
|
49
|
+
require "couchrest/model/associations"
|
50
|
+
require "couchrest/model/configuration"
|
51
|
+
|
52
|
+
# Monkey patches applied to couchrest
|
53
|
+
require "couchrest/model/support/couchrest"
|
54
|
+
require "couchrest/model/support/hash"
|
55
|
+
|
56
|
+
# Base libraries
|
57
|
+
require "couchrest/model/casted_model"
|
58
|
+
require "couchrest/model/base"
|
59
|
+
|
60
|
+
# Add rails support *after* everything has loaded
|
61
|
+
|
62
|
+
require "couchrest/railtie"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rails/generators/named_base'
|
2
|
+
require 'rails/generators/active_model'
|
3
|
+
require 'couchrest_model'
|
4
|
+
|
5
|
+
module CouchrestModel
|
6
|
+
module Generators
|
7
|
+
class Base < Rails::Generators::NamedBase #:nodoc:
|
8
|
+
|
9
|
+
# Set the current directory as base for the inherited generators.
|
10
|
+
def self.base_root
|
11
|
+
File.dirname(__FILE__)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rails/generators/couchrest_model'
|
2
|
+
|
3
|
+
module CouchrestModel
|
4
|
+
module Generators
|
5
|
+
class ModelGenerator < Base
|
6
|
+
check_class_collision
|
7
|
+
|
8
|
+
def create_model_file
|
9
|
+
template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_module_file
|
13
|
+
return if class_path.empty?
|
14
|
+
template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
|
15
|
+
end
|
16
|
+
|
17
|
+
hook_for :test_framework
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def parent_class_name
|
22
|
+
"CouchRest::Model::Base"
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
3
|
+
require File.join(FIXTURE_PATH, 'more', 'sale_invoice')
|
4
|
+
|
5
|
+
|
6
|
+
describe "Assocations" do
|
7
|
+
|
8
|
+
describe "of type belongs to" do
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
@invoice = SaleInvoice.create(:price => 2000)
|
12
|
+
@client = Client.create(:name => "Sam Lown")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should create a foreign key property with setter and getter" do
|
16
|
+
@invoice.properties.find{|p| p.name == 'client_id'}.should_not be_nil
|
17
|
+
@invoice.respond_to?(:client_id).should be_true
|
18
|
+
@invoice.respond_to?("client_id=").should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set the property and provide object when set" do
|
22
|
+
@invoice.client = @client
|
23
|
+
@invoice.client_id.should eql(@client.id)
|
24
|
+
@invoice.client.should eql(@client)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should set the attribute, save and return" do
|
28
|
+
@invoice.client = @client
|
29
|
+
@invoice.save
|
30
|
+
@invoice = SaleInvoice.get(@invoice.id)
|
31
|
+
@invoice.client.id.should eql(@client.id)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should remove the association if nil is provided" do
|
35
|
+
@invoice.client = @client
|
36
|
+
@invoice.client = nil
|
37
|
+
@invoice.client_id.should be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should not try to search for association if foreign_key is nil" do
|
41
|
+
@invoice.client_id = nil
|
42
|
+
Client.should_not_receive(:get)
|
43
|
+
@invoice.client
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should raise error if class name does not exist" do
|
47
|
+
lambda {
|
48
|
+
class TestBadAssoc < CouchRest::Model::Base
|
49
|
+
belongs_to :test_bad_item
|
50
|
+
end
|
51
|
+
}.should raise_error
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should allow override of foreign key" do
|
55
|
+
@invoice.respond_to?(:alternate_client).should be_true
|
56
|
+
@invoice.respond_to?("alternate_client=").should be_true
|
57
|
+
@invoice.properties.find{|p| p.name == 'alt_client_id'}.should_not be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should allow override of foreign key and save" do
|
61
|
+
@invoice.alternate_client = @client
|
62
|
+
@invoice.save
|
63
|
+
@invoice = SaleInvoice.get(@invoice.id)
|
64
|
+
@invoice.alternate_client.id.should eql(@client.id)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "of type collection_of" do
|
70
|
+
|
71
|
+
before(:each) do
|
72
|
+
@invoice = SaleInvoice.create(:price => 2000)
|
73
|
+
@entries = [
|
74
|
+
SaleEntry.create(:description => 'test line 1', :price => 500),
|
75
|
+
SaleEntry.create(:description => 'test line 2', :price => 500),
|
76
|
+
SaleEntry.create(:description => 'test line 3', :price => 1000)
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should create an associated property and collection proxy" do
|
81
|
+
@invoice.respond_to?('entry_ids')
|
82
|
+
@invoice.respond_to?('entry_ids=')
|
83
|
+
@invoice.entries.class.should eql(::CouchRest::CollectionOfProxy)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should allow replacement of objects" do
|
87
|
+
@invoice.entries = @entries
|
88
|
+
@invoice.entries.length.should eql(3)
|
89
|
+
@invoice.entry_ids.length.should eql(3)
|
90
|
+
@invoice.entries.first.should eql(@entries.first)
|
91
|
+
@invoice.entry_ids.first.should eql(@entries.first.id)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should allow ids to be set directly and load entries" do
|
95
|
+
@invoice.entry_ids = @entries.collect{|i| i.id}
|
96
|
+
@invoice.entries.length.should eql(3)
|
97
|
+
@invoice.entries.first.should eql(@entries.first)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should replace collection if ids replaced" do
|
101
|
+
@invoice.entry_ids = @entries.collect{|i| i.id}
|
102
|
+
@invoice.entries.length.should eql(3) # load once
|
103
|
+
@invoice.entry_ids = @entries[0..1].collect{|i| i.id}
|
104
|
+
@invoice.entries.length.should eql(2)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should allow forced collection update if ids changed" do
|
108
|
+
@invoice.entry_ids = @entries[0..1].collect{|i| i.id}
|
109
|
+
@invoice.entries.length.should eql(2) # load once
|
110
|
+
@invoice.entry_ids << @entries[2].id
|
111
|
+
@invoice.entry_ids.length.should eql(3)
|
112
|
+
@invoice.entries.length.should eql(2) # cached!
|
113
|
+
@invoice.entries(true).length.should eql(3)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should empty arrays when nil collection provided" do
|
117
|
+
@invoice.entries = @entries
|
118
|
+
@invoice.entries = nil
|
119
|
+
@invoice.entry_ids.should be_empty
|
120
|
+
@invoice.entries.should be_empty
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should empty arrays when nil ids array provided" do
|
124
|
+
@invoice.entries = @entries
|
125
|
+
@invoice.entry_ids = nil
|
126
|
+
@invoice.entry_ids.should be_empty
|
127
|
+
@invoice.entries.should be_empty
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should ignore nil entries" do
|
131
|
+
@invoice.entries = [ nil ]
|
132
|
+
@invoice.entry_ids.should be_empty
|
133
|
+
@invoice.entries.should be_empty
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "proxy" do
|
137
|
+
|
138
|
+
it "should ensure new entries to proxy are matched" do
|
139
|
+
@invoice.entries << @entries.first
|
140
|
+
@invoice.entry_ids.first.should eql(@entries.first.id)
|
141
|
+
@invoice.entries.first.should eql(@entries.first)
|
142
|
+
@invoice.entries << @entries[1]
|
143
|
+
@invoice.entries.count.should eql(2)
|
144
|
+
@invoice.entry_ids.count.should eql(2)
|
145
|
+
@invoice.entry_ids.last.should eql(@entries[1].id)
|
146
|
+
@invoice.entries.last.should eql(@entries[1])
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should support push method" do
|
150
|
+
@invoice.entries.push(@entries.first)
|
151
|
+
@invoice.entry_ids.first.should eql(@entries.first.id)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should support []= method" do
|
155
|
+
@invoice.entries[0] = @entries.first
|
156
|
+
@invoice.entry_ids.first.should eql(@entries.first.id)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should support unshift method" do
|
160
|
+
@invoice.entries.unshift(@entries.first)
|
161
|
+
@invoice.entry_ids.first.should eql(@entries.first.id)
|
162
|
+
@invoice.entries.unshift(@entries[1])
|
163
|
+
@invoice.entry_ids.first.should eql(@entries[1].id)
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should support pop method" do
|
167
|
+
@invoice.entries.push(@entries.first)
|
168
|
+
@invoice.entries.pop.should eql(@entries.first)
|
169
|
+
@invoice.entries.empty?.should be_true
|
170
|
+
@invoice.entry_ids.empty?.should be_true
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should support shift method" do
|
174
|
+
@invoice.entries.push(@entries[0])
|
175
|
+
@invoice.entries.push(@entries[1])
|
176
|
+
@invoice.entries.shift.should eql(@entries[0])
|
177
|
+
@invoice.entries.first.should eql(@entries[1])
|
178
|
+
@invoice.entry_ids.first.should eql(@entries[1].id)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should raise error when adding un-persisted entries" do
|
182
|
+
SaleEntry.find_by_description('test entry').should be_nil
|
183
|
+
entry = SaleEntry.new(:description => 'test entry', :price => 500)
|
184
|
+
lambda {
|
185
|
+
@invoice.entries << entry
|
186
|
+
}.should raise_error
|
187
|
+
# In the future maybe?
|
188
|
+
# @invoice.save.should be_true
|
189
|
+
# SaleEntry.find_by_description('test entry').should_not be_nil
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|