datts_right 0.0.1
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/.autotest +5 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +108 -0
- data/LICENSE.txt +20 -0
- data/README.textile +178 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/datts_right.gemspec +236 -0
- data/lib/datts_right.rb +98 -0
- data/lib/datts_right/datt.rb +40 -0
- data/lib/datts_right/exceptions.rb +10 -0
- data/lib/datts_right/instance_methods.rb +214 -0
- data/lib/datts_right/page.rb +3 -0
- data/lib/datts_right/query_methods.rb +15 -0
- data/lib/generators/migration/USAGE +8 -0
- data/lib/generators/migration/migration_generator.rb +3 -0
- data/spec/datt_spec.rb +20 -0
- data/spec/datts_right_spec.rb +299 -0
- data/spec/has_datts_migration_generator_spec.rb +13 -0
- data/spec/spec_helper.rb +43 -0
- metadata +948 -0
data/lib/datts_right.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'datts_right/instance_methods'
|
2
|
+
require 'datts_right/datt'
|
3
|
+
#require 'datts_right/query_methods'
|
4
|
+
#require 'datts_right/exceptions'
|
5
|
+
|
6
|
+
module DattsRight
|
7
|
+
def has_datts(options={})
|
8
|
+
include DattsRight::InstanceMethods
|
9
|
+
#include DattsRight::QueryMethods
|
10
|
+
#ActiveRecord::QueryMethods.extend DattsRight::QueryMethods
|
11
|
+
#include DattsRight::Exceptions
|
12
|
+
|
13
|
+
cattr_accessor :datts_options
|
14
|
+
self.datts_options = options
|
15
|
+
|
16
|
+
has_many :datts, :as => :attributable, :dependent => :destroy
|
17
|
+
|
18
|
+
# Carry out delayed actions before save
|
19
|
+
before_save :build_dynamic_attributes
|
20
|
+
|
21
|
+
# scope :scope_self when looking through attributes so we don't look through all datts
|
22
|
+
# Why? What if you have Friend and Page models.
|
23
|
+
# * Some Phone records have a datt :price
|
24
|
+
# * Some Page records have a datt :price
|
25
|
+
#
|
26
|
+
# When we do Page.find_by_price(400) we want to search only the datts that belong to Page
|
27
|
+
# and we want to disregard the rest of the datts.
|
28
|
+
scope :scope_self, lambda { joins(:datts).where("datts.attributable_type = :klass", :klass => self.name) }
|
29
|
+
scope :dattr_key, lambda { |attr_key| scope_self.joins(:datts).where("datts.attr_key = :attr_key", :attr_key => attr_key)}
|
30
|
+
scope :dattr_type, lambda { |object_type| scope_self.joins(:datts).where("object_type = :object_type", :object_type => object_type) }
|
31
|
+
|
32
|
+
scope :order_datt, lambda { |attr_key_with_order, object_type|
|
33
|
+
# possible attr_key_with_order forms: "field_name", "field_name ASC", "field_name DESC"
|
34
|
+
split_attr_key_with_order = attr_key_with_order.split(" ")
|
35
|
+
attr_key = split_attr_key_with_order.first
|
36
|
+
order_by = split_attr_key_with_order.last if split_attr_key_with_order.size > 1
|
37
|
+
order_value = "datts.#{object_type}_value"
|
38
|
+
order_value << " #{order_by}" if order_by
|
39
|
+
scope_self.dattr_key(attr_key).joins(:datts).dattr_type(object_type).order(order_value)
|
40
|
+
}
|
41
|
+
|
42
|
+
scope :where_datt, lambda { |opts|
|
43
|
+
# TODO accept stuff other than the normal hash
|
44
|
+
# Lifted from AR::Relation#build_where
|
45
|
+
attributes = case opts
|
46
|
+
when String, Array
|
47
|
+
self.expand_hash_conditions_for_aggregates(opts)
|
48
|
+
when Hash
|
49
|
+
opts
|
50
|
+
end
|
51
|
+
results = self#joins(:datts)
|
52
|
+
# Look for all DattsRight records whose:
|
53
|
+
# 1. Has all dynamic_attributes
|
54
|
+
# 2. Those dynamic attributes match there conditions
|
55
|
+
attributes.each do |k, v|
|
56
|
+
conditions = "exists (" +
|
57
|
+
"select 1 from datts datt where " +
|
58
|
+
# restricting the attributable_id to pages.id forces all the exists clauses to reference a single attributable record. Layman's terms, assuming Page uses datts_right:
|
59
|
+
# find only pages that has the matching dynamic attributes
|
60
|
+
"#{self.table_name}.id = datt.attributable_id " +
|
61
|
+
"and datt.attributable_type = :attributable_type " +
|
62
|
+
"and datt.name = :name and datt.#{Datt.attr_column(v)} = :value" +
|
63
|
+
")"
|
64
|
+
results = results.where(conditions, :attributable_type => self.name, :name => k.to_s, :value => v)
|
65
|
+
end
|
66
|
+
results
|
67
|
+
}
|
68
|
+
|
69
|
+
def self.method_missing(method_id, *arguments)
|
70
|
+
# TODO better way to hook this into the rails code, and not define my own
|
71
|
+
if method_id.to_s =~ /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
|
72
|
+
attributes = $2.split("_and_")
|
73
|
+
#puts "Will find by #{attributes.inspect}"
|
74
|
+
arg = arguments.first
|
75
|
+
scope_self.joins(:datts).where("datts.#{Datt.attr_column(arg)} = :value", :value => arg)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Override AR::Base#respond_to? so we can return the matchers even if the
|
80
|
+
# attribute doesn't exist in the actual columns. Is this expensive?
|
81
|
+
def respond_to?(method_id, include_private=false)
|
82
|
+
# TODO perhaps we could save a cache somewhere of all methods used by
|
83
|
+
# any of the records of this class. that way, we can make this method
|
84
|
+
# act a bit more like AR::Base#respond_to?
|
85
|
+
# Ex:
|
86
|
+
# return true if all_attributes_exists?(match.attribute_names) || all_dynamic_attributes_exists?(match.attribute_names)
|
87
|
+
if match = ActiveRecord::DynamicFinderMatch.match(method_id)
|
88
|
+
return true
|
89
|
+
elsif match = ActiveRecord::DynamicScopeMatch.match(method_id)
|
90
|
+
return true
|
91
|
+
end
|
92
|
+
|
93
|
+
super
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
ActiveRecord::Base.extend DattsRight
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Datt < ActiveRecord::Base
|
2
|
+
# TODO
|
3
|
+
# Set table name from configuration
|
4
|
+
belongs_to :attributable, :polymorphic => true
|
5
|
+
|
6
|
+
def value
|
7
|
+
send("#{object_type}_value")
|
8
|
+
end
|
9
|
+
|
10
|
+
def value=(v)
|
11
|
+
object_type = self.class.attr_type?(v)
|
12
|
+
|
13
|
+
# only allow object_type changes if this is NOT a new record
|
14
|
+
# and it's text to string or vice versa
|
15
|
+
interchangeable_types = %w(text string)
|
16
|
+
#puts "It's currently a #{self.object_type}, and we wanna make it a #{object_type}"
|
17
|
+
if !new_record? && interchangeable_types.include?(self.object_type) && interchangeable_types.include?(object_type) && self.object_type != object_type
|
18
|
+
#puts "It's not new, and we're setting it to #{object_type}"
|
19
|
+
self.object_type = object_type
|
20
|
+
end
|
21
|
+
send("#{self.class.attr_column(v)}=", v)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.attr_type?(v)
|
25
|
+
object_type = if v.is_a?(Integer)
|
26
|
+
"integer"
|
27
|
+
elsif v.is_a?(Float)
|
28
|
+
"float"
|
29
|
+
elsif v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
30
|
+
"boolean"
|
31
|
+
elsif v.is_a?(String)
|
32
|
+
v.size < 255 ? "string" : "text"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns what column this kind of data should be in
|
37
|
+
def self.attr_column(v)
|
38
|
+
"#{self.attr_type?(v)}_value"
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module DattsRight
|
2
|
+
module InstanceMethods
|
3
|
+
def add_dynamic_attribute(name, klass)
|
4
|
+
unless attributes.keys.include?(name.to_s)
|
5
|
+
name = name.to_s
|
6
|
+
datt = datts.find_by_attr_key(name.underscore)
|
7
|
+
unless datt
|
8
|
+
datt = datts.create :name => name, :attr_key => name.underscore, :object_type => klass
|
9
|
+
dynamic_columns(true)
|
10
|
+
end
|
11
|
+
#puts "Just added #{name} to #{self.class.name}##{id}, and here are the dynamic_attributes: #{dynamic_columns}"
|
12
|
+
datt
|
13
|
+
else
|
14
|
+
false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove_dynamic_attribute(name)
|
19
|
+
name = name.to_s
|
20
|
+
datt = datts.find_by_attr_key(name)
|
21
|
+
if datt
|
22
|
+
datt.destroy
|
23
|
+
dynamic_columns(true)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Works like ActiveRecord's attributes except it returns dynamic attributes only
|
28
|
+
def dynamic_attributes(reload=false)
|
29
|
+
@dynamic_attributes ||= {}
|
30
|
+
return @dynamic_attributes if !reload
|
31
|
+
#puts "Reloading dynamic_attributes"
|
32
|
+
datts.each do |datt|
|
33
|
+
#puts "Adding this to @dynamic_attributes: #{datt.attr_key} => #{datt.value}"
|
34
|
+
@dynamic_attributes.merge({datt.attr_key => datt.value})
|
35
|
+
end
|
36
|
+
#puts "Here are teh datts: #{datts.inspect}, and this is what we're returning: #{@dynamic_attributes}"
|
37
|
+
@dynamic_attributes.symbolize_keys!
|
38
|
+
end
|
39
|
+
|
40
|
+
# Because we cannot determine the difference between the inexistence
|
41
|
+
# between key value pair point to nil (eg :hi => nil) and
|
42
|
+
# the key not existing, we need to have a different method to return
|
43
|
+
# information about the record in this format:
|
44
|
+
# {:price => {:object_type => "integer", :value => 200}}
|
45
|
+
def dynamic_columns(reload=false)
|
46
|
+
@dynamic_columns ||= {}
|
47
|
+
@dynamic_columns if !reload
|
48
|
+
#puts "Building the dynamic_columns cache, and we'll look through #{datts.count} datts"
|
49
|
+
@dynamic_columns={}
|
50
|
+
datts.reload
|
51
|
+
datts.each do |datt|
|
52
|
+
#puts "Going through datt##{datt.id}"
|
53
|
+
dynamic_column = {
|
54
|
+
datt.attr_key.to_sym => {
|
55
|
+
:object_type => datt.object_type,
|
56
|
+
:value => datt.value
|
57
|
+
}
|
58
|
+
}
|
59
|
+
#puts "Added #{dynamic_column} to the dynamic columns"
|
60
|
+
@dynamic_columns.merge!(dynamic_column)
|
61
|
+
end
|
62
|
+
#puts "in dynamic_columns, returning: #{@dynamic_columns.inspect}"
|
63
|
+
@dynamic_columns
|
64
|
+
end
|
65
|
+
|
66
|
+
# Determines if the given attribute is a dynamic attribute.
|
67
|
+
def dynamic_attribute?(attr)
|
68
|
+
#puts "in dynamic_attribute?(:#{attr}). datts: #{datts.inspect}. Datt: #{Datt.all.inspect}"
|
69
|
+
#dynamic_columns.include?(attr.to_s)
|
70
|
+
!dynamic_columns[attr.to_sym].nil?
|
71
|
+
#return dynamic_attributes.include?(attr.to_sym) unless dynamic_attributes.empty?
|
72
|
+
#return false if self.class.column_names.include?(attr.to_s)
|
73
|
+
#false
|
74
|
+
end
|
75
|
+
|
76
|
+
# This overrides the attributes= defined in ActiveRecord::Base
|
77
|
+
# The only difference is that this doesn't check to see if the
|
78
|
+
# model responds_to the method before sending it
|
79
|
+
#def attributes=(new_attributes, guard_protected_attributes = true)
|
80
|
+
#return unless new_attributes.is_a?(Hash)
|
81
|
+
#attributes = new_attributes.stringify_keys
|
82
|
+
|
83
|
+
#multi_parameter_attributes = []
|
84
|
+
#attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
|
85
|
+
|
86
|
+
#attributes.each do |k, v|
|
87
|
+
#if k.include?("(")
|
88
|
+
#multi_parameter_attributes << [ k, v ]
|
89
|
+
#else
|
90
|
+
#send("#{k}=", v)
|
91
|
+
#end
|
92
|
+
#end
|
93
|
+
|
94
|
+
#assign_multiparameter_attributes(multi_parameter_attributes)
|
95
|
+
#end
|
96
|
+
|
97
|
+
# Overrides AR::Persistence#update_attributes
|
98
|
+
# Needed to change it because the AR::Persistence method updated the attributes
|
99
|
+
# method directly.
|
100
|
+
# Ex.
|
101
|
+
# self.attributes = attributes
|
102
|
+
#
|
103
|
+
# That won't work because our dynamic attributes aren't in the attributes field
|
104
|
+
def update_attributes(attributes)
|
105
|
+
# The following transaction covers any possible database side-effects of the
|
106
|
+
# attributes assignment. For example, setting the IDs of a child collection.
|
107
|
+
with_transaction_returning_status do
|
108
|
+
attributes.each do |k, v|
|
109
|
+
self.send("#{k}=", v)
|
110
|
+
end
|
111
|
+
save
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Overrides AR::Persistence#update_attributes!
|
116
|
+
# See DattsRight#update_attributes for the reason why
|
117
|
+
def update_attributes!(attributes)
|
118
|
+
with_transaction_returning_status do
|
119
|
+
attributes.each do |k, v|
|
120
|
+
self.send("#{k}=", v)
|
121
|
+
end
|
122
|
+
save!
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
# Called after validation on update so that dynamic attributes behave
|
129
|
+
# like normal attributes in the fact that the database is not touched
|
130
|
+
# until save is called.
|
131
|
+
def build_dynamic_attributes
|
132
|
+
return if @save_dynamic_attr.nil?
|
133
|
+
# This originally saves things into the "datts" column.
|
134
|
+
#write_attribute_without_dynamic_attributes "datts", @save_dynamic_attr
|
135
|
+
# We save things into the datts table
|
136
|
+
@save_dynamic_attr.each do |k, v|
|
137
|
+
k = k.to_s
|
138
|
+
datt = datts.find_by_attr_key(k)
|
139
|
+
#puts "to create or to update #{k}?"
|
140
|
+
if datt
|
141
|
+
datt.update_attribute(:value, v)
|
142
|
+
#puts "updated: #{datt.inspect}"
|
143
|
+
#else
|
144
|
+
#datt = datts.create! :name => k, :attr_key => k, :value => v
|
145
|
+
#puts "created: #{datt.inspect} among these datts: #{datts.inspect}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
@save_dynamic_attr = {}
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
# Implements dynamic-attributes as if real getter/setter methods
|
153
|
+
# were defined.
|
154
|
+
def method_missing(method_id, *args, &block)
|
155
|
+
#puts "in method_missing_without_dynamic_attributes. Checking #{method_id}"
|
156
|
+
begin
|
157
|
+
super method_id, *args, &block
|
158
|
+
rescue NoMethodError => e
|
159
|
+
attr_name = method_id.to_s.sub(/\=$/, '')
|
160
|
+
#puts "Now we check to see if price is a dynamic attribute. dynamic_columns: #{dynamic_columns}. is #{attr_name} in those dynamic_columns? #{dynamic_attribute?(attr_name)}"
|
161
|
+
if dynamic_attribute?(attr_name)
|
162
|
+
#puts "== And #{method_id} is a dynamic method"
|
163
|
+
if method_id.to_s =~ /\=$/
|
164
|
+
return write_attribute(attr_name, args[0])
|
165
|
+
else
|
166
|
+
return read_attribute(attr_name)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
raise e
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Overrides AR::Base#read_attribute
|
174
|
+
def read_attribute(attr_name)
|
175
|
+
attr_name = attr_name.to_s
|
176
|
+
if dynamic_attribute?(attr_name)
|
177
|
+
#puts "trying to read datt #{attr_name}"
|
178
|
+
# If value is in the @save_dynamic_attr cache, and it's not blank
|
179
|
+
if !@save_dynamic_attr.blank? and @save_dynamic_attr[attr_name]
|
180
|
+
#puts "datt #{attr_name} IS in cache, so let's return its value"
|
181
|
+
# Then we return the value
|
182
|
+
return @save_dynamic_attr[attr_name]
|
183
|
+
else # The value is not there, or it's blank
|
184
|
+
#puts "datt #{attr_name} is NOT in cache, so let's load it from the datt table"
|
185
|
+
#attrs = read_attribute_without_dynamic_attributes(dynamic_attributes_options[:column_name].to_s)
|
186
|
+
#puts "Here are ALL the datts: #{Datt.all.inspect} and my id is: #{id}"
|
187
|
+
#puts "Here are my the datts: #{datts.inspect} and my id is: #{id}. and the first datt's value is #{datts.first.value}"
|
188
|
+
datt = datts.find_by_attr_key(attr_name)
|
189
|
+
#puts "This is the dat I find: #{datt.inspect}"
|
190
|
+
return datt.try(:value)
|
191
|
+
#attrs = attrs.nil? ? nil : YAML.load(attrs).symbolize_keys! unless attrs.is_a? Hash
|
192
|
+
#return nil if attrs.blank?
|
193
|
+
#return attrs[attr_name.to_sym]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
super(attr_name)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Overrides AR::Base#write_attribute
|
201
|
+
def write_attribute(attr_name, value)
|
202
|
+
#puts "attempting to write: #{attr_name} = #{value}"
|
203
|
+
if dynamic_attribute?(attr_name)
|
204
|
+
#puts "#{attr_name} is a dynamic_attribute"
|
205
|
+
attr_name = attr_name.to_s
|
206
|
+
|
207
|
+
@save_dynamic_attr ||= {} # create the cache if needed
|
208
|
+
return @save_dynamic_attr[attr_name] = value
|
209
|
+
end
|
210
|
+
|
211
|
+
super(attr_name, value)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module DattsRight
|
2
|
+
module QueryMethods
|
3
|
+
puts "in DattsRight::QueryMethods"
|
4
|
+
def order(*args)
|
5
|
+
puts "In DattsRight::QueryMethods#order with these args: #{args}"
|
6
|
+
relation = clone
|
7
|
+
puts "= This is the relation: #{relation.inspect}"
|
8
|
+
relation.order_values += args.flatten unless args.blank?
|
9
|
+
puts "= This is the relation.order_values: #{relation.order_values.inspect}"
|
10
|
+
relation
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveRecord::QueryMethods.extend DattsRight::QueryMethods
|
data/spec/datt_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Datt do
|
4
|
+
before do
|
5
|
+
reset_database
|
6
|
+
@page = Page.create
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should allow transferring between String and Text" do
|
10
|
+
@page.add_dynamic_attribute(:foo, "string")
|
11
|
+
@page.foo = "this is a string"
|
12
|
+
@page.save
|
13
|
+
@page.foo = "t"*300
|
14
|
+
@page.save
|
15
|
+
@page.datts.find_by_attr_key("foo").object_type.should == "text"
|
16
|
+
@page.foo = "this is a string again"
|
17
|
+
@page.save
|
18
|
+
@page.datts.find_by_attr_key("foo").object_type.should == "string"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,299 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe DattsRight do
|
4
|
+
before do
|
5
|
+
reset_database
|
6
|
+
@page = Page.create
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".add_dynamic_attribute(attr_key, object_type)" do
|
10
|
+
it "should add a dynamic attribute with nil value" do
|
11
|
+
@page.add_dynamic_attribute(:rocks, "string")
|
12
|
+
@page.dynamic_attributes[:rocks].should be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should ignore when trying to add same attribute" do
|
16
|
+
@page.add_dynamic_attribute(:rocks, "string")
|
17
|
+
@page.add_dynamic_attribute(:rocks, "integer")
|
18
|
+
@page.dynamic_columns[:rocks][:object_type].should == "string"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return false if the method that is being added already exists" do
|
22
|
+
@page.add_dynamic_attribute(:name, "text").should be_false
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not make any changes to the original attribute" do
|
26
|
+
@page.update_attribute(:name, "juno")
|
27
|
+
@page.add_dynamic_attribute(:name, "text").should be_false
|
28
|
+
@page.name.should == "juno"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe ".remove_dynamic_attribute(attr_key)" do
|
33
|
+
it "should remove the attribute completely" do
|
34
|
+
@page.add_dynamic_attribute(:rocks, "string")
|
35
|
+
@page.remove_dynamic_attribute(:rocks)
|
36
|
+
lambda { @page.rocks }.should raise_error(NoMethodError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not explode if the attribute being removed isn't there" do
|
40
|
+
@page.add_dynamic_attribute(:rocks, "string")
|
41
|
+
@page.remove_dynamic_attribute(:rocks)
|
42
|
+
lambda {
|
43
|
+
@page.remove_dynamic_attribute(:rocks)
|
44
|
+
}.should_not raise_error(NoMethodError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ".dynamic_columns(reload)" do
|
49
|
+
it "should return an array of symbols of the available dynamic columns" do
|
50
|
+
@page.add_dynamic_attribute(:rocks, "string")
|
51
|
+
@page.is_dynamic_attribute?(:rocks).should be_true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not allow arbitrary creation of attributes" do
|
56
|
+
lambda {
|
57
|
+
@page.some_field = "woot"
|
58
|
+
}.should raise_error(NoMethodError)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should allow saving of integers" do
|
62
|
+
@page.add_dynamic_attribute(:price, "integer")
|
63
|
+
@page.price = 300
|
64
|
+
@page.save
|
65
|
+
@page = Page.last
|
66
|
+
@page.price.should == 300
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should allow saving of floats" do
|
70
|
+
@page.add_dynamic_attribute(:price, "float")
|
71
|
+
@page.price = 300.0
|
72
|
+
@page.save
|
73
|
+
@page = Page.last
|
74
|
+
@page.price.should == 300.0
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should allow saving of true" do
|
78
|
+
@page.add_dynamic_attribute(:real, "boolean")
|
79
|
+
@page.real = true
|
80
|
+
@page.save
|
81
|
+
@page = Page.last
|
82
|
+
@page.real.should be_true
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should allow saving of false" do
|
86
|
+
@page.add_dynamic_attribute(:real, "boolean")
|
87
|
+
@page.real = false
|
88
|
+
@page.save
|
89
|
+
@page = Page.last
|
90
|
+
@page.real.should be_false
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should allow saving of strings" do
|
94
|
+
@page.add_dynamic_attribute(:real, "string")
|
95
|
+
@page.real = "its real alright"
|
96
|
+
@page.save
|
97
|
+
@page = Page.last
|
98
|
+
@page.real.should == "its real alright"
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should allow saving of text" do
|
102
|
+
@page.add_dynamic_attribute(:real, "text")
|
103
|
+
@page.real = "t"*256
|
104
|
+
@page.save
|
105
|
+
@page = Page.last
|
106
|
+
@page.real.should == "t"*256
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should cache the attribute" do
|
110
|
+
@page.add_dynamic_attribute(:price, "integer")
|
111
|
+
@page.price = 200
|
112
|
+
@page.price.should == 200
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should not save the attribute if save on the attributable object isn't called" do
|
116
|
+
@page.add_dynamic_attribute(:price, "integer")
|
117
|
+
@page.price = 200
|
118
|
+
@page = Page.last
|
119
|
+
@page.price.should be_nil
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should allow overwriting of values" do
|
123
|
+
@page.add_dynamic_attribute(:price, "integer")
|
124
|
+
@page.price = 200
|
125
|
+
@page.save
|
126
|
+
@page = Page.last
|
127
|
+
@page.price = 300
|
128
|
+
@page.save
|
129
|
+
@page = Page.last
|
130
|
+
@page.price.should == 300
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should allow nullifying of attributes, but keeping the fields there" do
|
134
|
+
@page.add_dynamic_attribute(:farce, "string")
|
135
|
+
@page.farce = "Nothing here my friend"
|
136
|
+
@page.save
|
137
|
+
@page.farce = nil
|
138
|
+
@page.is_dynamic_attribute?(:farce).should be_true
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "on dynamic find_by methods" do
|
142
|
+
it "should work" do
|
143
|
+
@page.add_dynamic_attribute(:price, "integer")
|
144
|
+
@page.price = 400
|
145
|
+
@page.save
|
146
|
+
|
147
|
+
@page_2 = Page.create
|
148
|
+
@page_2.add_dynamic_attribute(:price, "integer")
|
149
|
+
@page_2.price = 500
|
150
|
+
@page_2.save
|
151
|
+
|
152
|
+
Page.find_by_price(400).should include(@page)
|
153
|
+
Page.find_by_price(400).should_not include(@page_2)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should allow chaining" do
|
157
|
+
@page.add_dynamic_attribute(:price, "integer")
|
158
|
+
@page.name = "fixed"
|
159
|
+
@page.price = 400
|
160
|
+
@page.save
|
161
|
+
|
162
|
+
@page_2 = Page.create
|
163
|
+
@page_2.add_dynamic_attribute(:price, "integer")
|
164
|
+
@page_2.price = 500
|
165
|
+
@page_2.save
|
166
|
+
|
167
|
+
@pages = Page.where(:name => "fixed").find_by_price(400)
|
168
|
+
@pages.should include(@page)
|
169
|
+
@pages.should_not include(@page_2)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should allow multiple attributes" do
|
174
|
+
@page.add_dynamic_attribute(:price, "integer")
|
175
|
+
@page.price = 400
|
176
|
+
@page.add_dynamic_attribute(:farce, "string")
|
177
|
+
@page.farce = "hi"
|
178
|
+
@page.save
|
179
|
+
|
180
|
+
@page_2 = Page.create
|
181
|
+
@page_2.add_dynamic_attribute(:price, "integer")
|
182
|
+
@page_2.price = 400
|
183
|
+
@page_2.add_dynamic_attribute(:farce, "string")
|
184
|
+
@page_2.farce = "hitt"
|
185
|
+
@page_2.save
|
186
|
+
|
187
|
+
@pages = Page.find_by_farce_and_price("hi", 400)
|
188
|
+
@pages.should include(@page)
|
189
|
+
@pages.should_not include(@page_2)
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "when the value being assigned is not of the same type (with exceptions)" do
|
193
|
+
before do
|
194
|
+
@page.add_dynamic_attribute(:price, "integer")
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should not save the value" do
|
198
|
+
@page.price = 200
|
199
|
+
@page.save
|
200
|
+
@page.price = "hi there"
|
201
|
+
@page.save
|
202
|
+
Page.last.price.should == 200
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should allow mass assignment" do
|
207
|
+
@page.add_dynamic_attribute(:price, "integer")
|
208
|
+
@page.add_dynamic_attribute(:farce, "string")
|
209
|
+
@page.update_attributes(:price => 200, :farce => "hi")
|
210
|
+
@page.price.should == 200
|
211
|
+
@page.farce.should == "hi"
|
212
|
+
|
213
|
+
@page.update_attributes!(:price => 300, :farce => "not farce!")
|
214
|
+
@page.price.should == 300
|
215
|
+
@page.farce.should == "not farce!"
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should allow use of update_attribute" do
|
219
|
+
@page.add_dynamic_attribute(:price, "integer")
|
220
|
+
@page.update_attribute(:price, 200)
|
221
|
+
@page.price.should == 200
|
222
|
+
end
|
223
|
+
|
224
|
+
describe "ordering" do
|
225
|
+
it "should default to asc" do
|
226
|
+
@page.add_dynamic_attribute(:price, "integer")
|
227
|
+
@page.price = 2
|
228
|
+
@page.save
|
229
|
+
@page_2 = Page.create
|
230
|
+
@page_2.add_dynamic_attribute(:price, "integer")
|
231
|
+
@page_2.price = 1
|
232
|
+
@page_2.save
|
233
|
+
@page_3 = Page.create
|
234
|
+
@page_3.add_dynamic_attribute(:price, "float")
|
235
|
+
@page_3.price = 3.0
|
236
|
+
@page_3.save
|
237
|
+
@page_4 = Page.create
|
238
|
+
@page_4.add_dynamic_attribute(:price, "float")
|
239
|
+
@page_4.price = 3.5
|
240
|
+
@page_4.save
|
241
|
+
|
242
|
+
# What if different records have price but they are of different object_type?
|
243
|
+
# page_1.price is an "integer" and it's saved in "integer_value" column
|
244
|
+
# page_2.price is a "float" and it's saved in "float_value"
|
245
|
+
#
|
246
|
+
# How would you work on Page.order_datt("price")?
|
247
|
+
#
|
248
|
+
# Answer 1: we could require passing of the object_type in the order method
|
249
|
+
# This way, we know which column to look at => order_datt("price", "integer")
|
250
|
+
Page.order_datt("price", "integer").should == [@page_2, @page]
|
251
|
+
Page.order_datt("price DESC", "float").should == [@page_4, @page_3]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
describe "where_datt" do
|
256
|
+
it "should automatically join in the datts table" do
|
257
|
+
@page.add_dynamic_attribute(:price, "float")
|
258
|
+
@page.price = 200.0
|
259
|
+
@page.add_dynamic_attribute(:farce, "boolean")
|
260
|
+
@page.farce = true
|
261
|
+
@page.save
|
262
|
+
|
263
|
+
@page_2 = Page.create
|
264
|
+
@page_2.add_dynamic_attribute(:price, "integer")
|
265
|
+
@page_2.add_dynamic_attribute(:farce, "boolean")
|
266
|
+
@page_2.farce = true
|
267
|
+
@page_2.price = 1
|
268
|
+
@page_2.save
|
269
|
+
|
270
|
+
@result = Page.where_datt(:price => 200.0, :farce => true)
|
271
|
+
@result.should include(@page)
|
272
|
+
@result.should_not include(@page_2)
|
273
|
+
|
274
|
+
#@result = Page.where_datt("price < :price", :price => 199.0)
|
275
|
+
#@result.should_not include(@page)
|
276
|
+
#@result.should include(@page_2)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should allow chaining with where scopes" do
|
280
|
+
@page.add_dynamic_attribute(:price, "integer")
|
281
|
+
@page.name = "aardvark"
|
282
|
+
@page.price = 200
|
283
|
+
@page.save
|
284
|
+
|
285
|
+
@page_2 = Page.create
|
286
|
+
@page_2.add_dynamic_attribute(:price, "integer")
|
287
|
+
@page_2.price = 200
|
288
|
+
@page_2.save
|
289
|
+
|
290
|
+
@results = Page.where_datt(:price => 200).where(:name => "aardvark")
|
291
|
+
@results.should include(@page)
|
292
|
+
@results.should_not include(@page_2)
|
293
|
+
end
|
294
|
+
|
295
|
+
it "should return an empty array if we're looking for a field that doesn't exist" do
|
296
|
+
Page.where_datt(:bogus_field => "none").should be_empty
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|