datts_right 0.0.15 → 0.0.16
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/Gemfile.lock +1 -1
- data/README.textile +55 -5
- data/VERSION +1 -1
- data/datts_right.gemspec +27 -2
- data/lib/datts_right.rb +3 -113
- data/lib/datts_right/base.rb +117 -0
- data/lib/datts_right/category.rb +4 -0
- data/lib/datts_right/dynamic_attribute.rb +8 -0
- data/lib/datts_right/dynamic_attribute_definition.rb +10 -0
- data/lib/datts_right/instance_methods.rb +29 -37
- data/lib/datts_right/page.rb +2 -1
- data/spec/datts_right/dynamic_attribute_definition_spec.rb +10 -0
- data/spec/datts_right/inheritance_spec.rb +21 -0
- data/spec/spec_helper.rb +12 -2
- metadata +101 -4
data/Gemfile.lock
CHANGED
data/README.textile
CHANGED
@@ -20,7 +20,7 @@ h2. Installation
|
|
20
20
|
Create a migration:
|
21
21
|
|
22
22
|
<pre>
|
23
|
-
class
|
23
|
+
class CreateDynamicAttributes < ActiveRecord::Migration
|
24
24
|
def self.up
|
25
25
|
create_table(:dynamic_attributes) do |t|
|
26
26
|
t.string :name, :null => false
|
@@ -36,9 +36,23 @@ class CreateDatts < ActiveRecord::Migration
|
|
36
36
|
add_index "dynamic_attributes", ["attributable_id"], :name => "index_dynamic_attributes_on_attributable_id"
|
37
37
|
add_index "dynamic_attributes", ["attributable_type"], :name => "index_dynamic_attributes_on_attributable_type"
|
38
38
|
add_index "dynamic_attributes", ["attr_key"], :name => "index_dynamic_attributes_on_attr_key"
|
39
|
+
|
40
|
+
create_table(:dynamic_attribute_definitions) do |t|
|
41
|
+
t.text :definition
|
42
|
+
t.string :attribute_defineable_type, :null => false
|
43
|
+
t.integer :attribute_defineable_id, :null => false
|
44
|
+
end
|
45
|
+
|
46
|
+
add_index "dynamic_attribute_definitions", ["attribute_defineable_id"], :name => "index_dynamic_attribute_definitions_on_attribute_defineable_id"
|
47
|
+
add_index "dynamic_attribute_definitions", ["attribute_defineable_type"], :name => "index_dynamic_attribute_definitions_on_attribute_defineable_type"
|
39
48
|
end
|
40
49
|
|
41
50
|
def self.down
|
51
|
+
remove_index "dynamic_attribute_definitions", :name => "index_dynamic_attribute_definitions_on_attribute_defineable_id"
|
52
|
+
remove_index "dynamic_attribute_definitions", :name => "index_dynamic_attribute_definitions_on_attribute_defineable_type"
|
53
|
+
|
54
|
+
drop_table(:dynamic_attribute_definitions)
|
55
|
+
|
42
56
|
remove_index "dynamic_attributes", :name => "index_dynamic_attributes_on_attr_key"
|
43
57
|
remove_index "dynamic_attributes", :name => "index_dynamic_attributes_on_attributable_type"
|
44
58
|
remove_index "dynamic_attributes", :name => "index_dynamic_attributes_on_attributable_id"
|
@@ -48,7 +62,6 @@ class CreateDatts < ActiveRecord::Migration
|
|
48
62
|
end
|
49
63
|
</pre>
|
50
64
|
|
51
|
-
* *name* is not important at the moment, but I intend to make better use of it in the future.
|
52
65
|
* *attr_key* the name of the dynamic attribute
|
53
66
|
* *object_type* what type of value is saved. See "object_type":#object_type.
|
54
67
|
* *attributable_id* and *attributable_type* are for the polymorphic association
|
@@ -78,9 +91,7 @@ pre. @user = User.create :name => "Roland Deschain"
|
|
78
91
|
@user.write_dynamic_attribute :age, 820
|
79
92
|
@user.save
|
80
93
|
|
81
|
-
h3. @instance.
|
82
|
-
|
83
|
-
However, dynamic_attributes doesn't give us much information. What if we want to find out the list of dynamic attributes already available?
|
94
|
+
h3. @instance.dynamic_attribute_details
|
84
95
|
|
85
96
|
pre. @user.dynamic_attribute_details(:age) # Returns a dynamic attribute record, where you can access the following:
|
86
97
|
@user.dynamic_attribute_details(:age).object_type # "integer"
|
@@ -99,6 +110,45 @@ You can also use:
|
|
99
110
|
pre. find_all_by_dynamic_attribute
|
100
111
|
find_last_by_dynamic_attribute
|
101
112
|
|
113
|
+
h3. Mimicking ActiveRecord
|
114
|
+
|
115
|
+
You can read and write the attributes directly:
|
116
|
+
|
117
|
+
@page.age = 200 # here, age is a dynamic attribute
|
118
|
+
@page.age # returns 200
|
119
|
+
|
120
|
+
h3. Defining attributes
|
121
|
+
|
122
|
+
If you want to define what attributes will exist, you can set an associated record to define what will attributes will automatically exist. Best explained by example:
|
123
|
+
|
124
|
+
<pre>
|
125
|
+
class Category < AR::Base
|
126
|
+
has_dynamic_attributes :definition => true
|
127
|
+
has_many :pages
|
128
|
+
end
|
129
|
+
|
130
|
+
class Page < AR::Base
|
131
|
+
has_dynamic_attributes :of => :category
|
132
|
+
belongs_to :category
|
133
|
+
end
|
134
|
+
</pre>
|
135
|
+
|
136
|
+
pre. @category = Category.create
|
137
|
+
@category.definition = {:price => {:object_type => "integer"}, :description => {:object_type => "text"}}
|
138
|
+
@category.save
|
139
|
+
@page = Page.create :category => category
|
140
|
+
@page.price = 200
|
141
|
+
@page.description = "hi there"
|
142
|
+
|
143
|
+
What happened here was that for every definition in category, a dynamic attribute was created on every page that belongs to that category. These dynamic attributes are added to page after_create. That means if you change the definition of the category afterwards, then it won't affect the pages that already exist.
|
144
|
+
|
145
|
+
The only thing that is *required* is @:price => {:object_type => "integer"}@ - you may add more information like: @:price => {:object_type => "integer", :name => "Price", :description => "How much this will cost."}@
|
146
|
+
|
147
|
+
You can access all that other information this way:
|
148
|
+
|
149
|
+
pre. @page.dynamic_attribute_details(:price).definer[:name] # "Price"
|
150
|
+
@page.dynamic_attribute_details(:price).definer[:description] # "How much this will cost."
|
151
|
+
|
102
152
|
h2. Stuff that make things faster
|
103
153
|
|
104
154
|
The dynamic attributes are only actually saved when save is called on the record that has them.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.16
|
data/datts_right.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{datts_right}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.16"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Ramon Tayag"]
|
12
|
-
s.date = %q{2011-04-
|
12
|
+
s.date = %q{2011-04-19}
|
13
13
|
s.description = %q{Creates a separate table that saves all your dynamic attributes.}
|
14
14
|
s.email = %q{ramon@tayag.net}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -29,7 +29,10 @@ Gem::Specification.new do |s|
|
|
29
29
|
"VERSION",
|
30
30
|
"datts_right.gemspec",
|
31
31
|
"lib/datts_right.rb",
|
32
|
+
"lib/datts_right/base.rb",
|
33
|
+
"lib/datts_right/category.rb",
|
32
34
|
"lib/datts_right/dynamic_attribute.rb",
|
35
|
+
"lib/datts_right/dynamic_attribute_definition.rb",
|
33
36
|
"lib/datts_right/exceptions.rb",
|
34
37
|
"lib/datts_right/instance_methods.rb",
|
35
38
|
"lib/datts_right/page.rb",
|
@@ -37,6 +40,8 @@ Gem::Specification.new do |s|
|
|
37
40
|
"spec/datt_spec.rb",
|
38
41
|
"spec/datts_right/add_dynamic_attributes_spec.rb",
|
39
42
|
"spec/datts_right/attributes_spec.rb",
|
43
|
+
"spec/datts_right/dynamic_attribute_definition_spec.rb",
|
44
|
+
"spec/datts_right/inheritance_spec.rb",
|
40
45
|
"spec/datts_right/remove_dynamic_attribute_spec.rb",
|
41
46
|
"spec/datts_right_spec.rb",
|
42
47
|
"spec/spec_helper.rb"
|
@@ -50,6 +55,8 @@ Gem::Specification.new do |s|
|
|
50
55
|
"spec/datt_spec.rb",
|
51
56
|
"spec/datts_right/add_dynamic_attributes_spec.rb",
|
52
57
|
"spec/datts_right/attributes_spec.rb",
|
58
|
+
"spec/datts_right/dynamic_attribute_definition_spec.rb",
|
59
|
+
"spec/datts_right/inheritance_spec.rb",
|
53
60
|
"spec/datts_right/remove_dynamic_attribute_spec.rb",
|
54
61
|
"spec/datts_right_spec.rb",
|
55
62
|
"spec/spec_helper.rb"
|
@@ -230,6 +237,12 @@ Gem::Specification.new do |s|
|
|
230
237
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
231
238
|
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
232
239
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
240
|
+
s.add_development_dependency(%q<autotest>, [">= 0"])
|
241
|
+
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
242
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
243
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
244
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
245
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
233
246
|
else
|
234
247
|
s.add_dependency(%q<datts_right>, [">= 0"])
|
235
248
|
s.add_dependency(%q<rails>, [">= 3.0.0"])
|
@@ -402,6 +415,12 @@ Gem::Specification.new do |s|
|
|
402
415
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
403
416
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
404
417
|
s.add_dependency(%q<rcov>, [">= 0"])
|
418
|
+
s.add_dependency(%q<autotest>, [">= 0"])
|
419
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
420
|
+
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
421
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
422
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
423
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
405
424
|
end
|
406
425
|
else
|
407
426
|
s.add_dependency(%q<datts_right>, [">= 0"])
|
@@ -575,6 +594,12 @@ Gem::Specification.new do |s|
|
|
575
594
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
576
595
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
577
596
|
s.add_dependency(%q<rcov>, [">= 0"])
|
597
|
+
s.add_dependency(%q<autotest>, [">= 0"])
|
598
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
599
|
+
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
600
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
601
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
602
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
578
603
|
end
|
579
604
|
end
|
580
605
|
|
data/lib/datts_right.rb
CHANGED
@@ -1,116 +1,6 @@
|
|
1
|
+
require 'datts_right/base'
|
1
2
|
require 'datts_right/instance_methods'
|
2
3
|
require 'datts_right/dynamic_attribute'
|
4
|
+
require 'datts_right/dynamic_attribute_definition'
|
3
5
|
|
4
|
-
|
5
|
-
def has_dynamic_attributes(options={})
|
6
|
-
include DattsRight::InstanceMethods
|
7
|
-
cattr_accessor :dynamic_attributes_options
|
8
|
-
self.dynamic_attributes_options = options
|
9
|
-
|
10
|
-
has_many :dynamic_attributes, :as => :attributable, :dependent => :destroy
|
11
|
-
|
12
|
-
# Carry out delayed actions before save
|
13
|
-
before_save :build_dynamic_attributes
|
14
|
-
|
15
|
-
after_find :cache_dynamic_attributes
|
16
|
-
default_scope includes(:dynamic_attributes)
|
17
|
-
|
18
|
-
|
19
|
-
# scope :scope_self when looking through attributes so we don't look through all dynamic_attributes
|
20
|
-
# Why? What if you have Friend and Page models.
|
21
|
-
# * Some Phone records have a dynamic_attribute :price
|
22
|
-
# * Some Page records have a dynamic_attribute :price
|
23
|
-
#
|
24
|
-
# When we do Page.find_by_price(400) we want to search only the dynamic_attributes that belong to Page
|
25
|
-
# and we want to disregard the rest of the dynamic_attributes.
|
26
|
-
scope :scope_self, lambda { joins(:dynamic_attributes).where("dynamic_attributes.attributable_type = :klass", :klass => self.name) }
|
27
|
-
scope :with_datt_key, lambda { |args| with_dynamic_attribute_key(args) }
|
28
|
-
scope :with_dynamic_attribute_key, lambda { |datt_key| scope_self.joins(:dynamic_attributes).where("dynamic_attributes.attr_key = :datt_key", :datt_key => datt_key)}
|
29
|
-
scope :with_datt_type, lambda { |args| with_dynamic_attribute_type(args) }
|
30
|
-
scope :with_dynamic_attribute_type, lambda { |object_type| scope_self.joins(:dynamic_attributes).where("object_type = :object_type", :object_type => object_type) }
|
31
|
-
|
32
|
-
scope :order_by_datt, lambda { |attr_key_with_order, object_type| order_by_dynamic_attribute(attr_key_with_order, object_type) }
|
33
|
-
scope :order_by_dynamic_attribute, lambda { |attr_key_with_order, object_type|
|
34
|
-
# possible attr_key_with_order forms: "field_name", "field_name ASC", "field_name DESC"
|
35
|
-
split_attr_key_with_order = attr_key_with_order.split(" ")
|
36
|
-
attr_key = split_attr_key_with_order.first
|
37
|
-
order_by = split_attr_key_with_order.last if split_attr_key_with_order.size > 1
|
38
|
-
order_value = "dynamic_attributes.#{object_type}_value"
|
39
|
-
order_value << " #{order_by}" if order_by
|
40
|
-
scope_self.with_dynamic_attribute_key(attr_key).joins(:dynamic_attributes).with_dynamic_attribute_type(object_type).order(order_value)
|
41
|
-
}
|
42
|
-
|
43
|
-
scope :where_datt, lambda { |opts| where_dynamic_attribute(opts) }
|
44
|
-
scope :where_dynamic_attribute, lambda { |opts|
|
45
|
-
# TODO accept stuff other than the normal hash
|
46
|
-
# Lifted from AR::Relation#build_where
|
47
|
-
attributes = case opts
|
48
|
-
when String, Array
|
49
|
-
self.expand_hash_conditions_for_aggregates(opts)
|
50
|
-
when Hash
|
51
|
-
opts
|
52
|
-
end
|
53
|
-
results = self
|
54
|
-
attributes.each do |k, v|
|
55
|
-
conditions = "exists (" +
|
56
|
-
"select 1 from dynamic_attributes dynamic_attribute where " +
|
57
|
-
"#{self.table_name}.id = dynamic_attribute.attributable_id " +
|
58
|
-
"and dynamic_attribute.attributable_type = :attributable_type " +
|
59
|
-
"and dynamic_attribute.name = :name and dynamic_attribute.#{DynamicAttribute.attr_column(v)} = :value" +
|
60
|
-
")"
|
61
|
-
results = results.where(conditions, :attributable_type => self.name, :name => k.to_s, :value => v)
|
62
|
-
end
|
63
|
-
results
|
64
|
-
}
|
65
|
-
|
66
|
-
def self.method_missing(method_id, *arguments)
|
67
|
-
# TODO better way to hook this into the rails code, and not define my own
|
68
|
-
begin # Prioritize ActiveRecord's method_missing
|
69
|
-
super(method_id, *arguments)
|
70
|
-
rescue NoMethodError => e
|
71
|
-
if method_id.to_s =~ /^find_(all_|last_)?by_(dynamic_attribute|datt)_([_a-z]\w*)$/
|
72
|
-
all_or_last = $1
|
73
|
-
attributes = $3.split("_and_")
|
74
|
-
results = self
|
75
|
-
attributes.each_with_index do |attribute, i|
|
76
|
-
results = results.where_dynamic_attribute(attribute.to_sym => arguments[i])
|
77
|
-
end
|
78
|
-
|
79
|
-
case all_or_last
|
80
|
-
when "all_"
|
81
|
-
results
|
82
|
-
when "last_"
|
83
|
-
results.last
|
84
|
-
when nil
|
85
|
-
results.first
|
86
|
-
else
|
87
|
-
nil
|
88
|
-
end
|
89
|
-
else
|
90
|
-
raise e
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# Override AR::Base#respond_to? so we can return the matchers even if the
|
96
|
-
# attribute doesn't exist in the actual columns. Is this expensive?
|
97
|
-
def respond_to?(method_id, include_private=false)
|
98
|
-
# TODO perhaps we could save a cache somewhere of all methods used by
|
99
|
-
# any of the records of this class. that way, we can make this method
|
100
|
-
# act a bit more like AR::Base#respond_to?
|
101
|
-
# Ex:
|
102
|
-
# return true if all_attributes_exists?(match.attribute_names) || all_dynamic_attributes_exists?(match.attribute_names)
|
103
|
-
if match = ActiveRecord::DynamicFinderMatch.match(method_id)
|
104
|
-
return true
|
105
|
-
elsif match = ActiveRecord::DynamicScopeMatch.match(method_id)
|
106
|
-
return true
|
107
|
-
end
|
108
|
-
|
109
|
-
super
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
alias :has_datts :has_dynamic_attributes
|
114
|
-
end
|
115
|
-
|
116
|
-
ActiveRecord::Base.extend DattsRight
|
6
|
+
ActiveRecord::Base.extend DattsRight::Base
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module DattsRight
|
2
|
+
module Base
|
3
|
+
def has_dynamic_attributes(options={})
|
4
|
+
include DattsRight::InstanceMethods
|
5
|
+
cattr_accessor :dynamic_attributes_options
|
6
|
+
self.dynamic_attributes_options = options
|
7
|
+
|
8
|
+
has_many :dynamic_attributes, :as => :attributable, :dependent => :destroy
|
9
|
+
has_one :dynamic_attribute_definition, :as => :attribute_defineable, :dependent => :destroy
|
10
|
+
after_save :save_dynamic_attribute_definition
|
11
|
+
after_create :create_dynamic_attribute_definition, :inherit_definition
|
12
|
+
delegate :definition, :definition=, :to => :dynamic_attribute_definition
|
13
|
+
|
14
|
+
# Carry out delayed actions before save
|
15
|
+
before_save :build_dynamic_attributes
|
16
|
+
|
17
|
+
after_find :cache_dynamic_attributes
|
18
|
+
default_scope includes(:dynamic_attributes).includes(:dynamic_attribute_definition)
|
19
|
+
|
20
|
+
|
21
|
+
# scope :scope_self when looking through attributes so we don't look through all dynamic_attributes
|
22
|
+
# Why? What if you have Friend and Page models.
|
23
|
+
# * Some Phone records have a dynamic_attribute :price
|
24
|
+
# * Some Page records have a dynamic_attribute :price
|
25
|
+
#
|
26
|
+
# When we do Page.find_by_price(400) we want to search only the dynamic_attributes that belong to Page
|
27
|
+
# and we want to disregard the rest of the dynamic_attributes.
|
28
|
+
scope :scope_self, lambda { joins(:dynamic_attributes).where("dynamic_attributes.attributable_type = :klass", :klass => self.name) }
|
29
|
+
scope :with_datt_key, lambda { |args| with_dynamic_attribute_key(args) }
|
30
|
+
scope :with_dynamic_attribute_key, lambda { |datt_key| scope_self.joins(:dynamic_attributes).where("dynamic_attributes.attr_key = :datt_key", :datt_key => datt_key)}
|
31
|
+
scope :with_datt_type, lambda { |args| with_dynamic_attribute_type(args) }
|
32
|
+
scope :with_dynamic_attribute_type, lambda { |object_type| scope_self.joins(:dynamic_attributes).where("object_type = :object_type", :object_type => object_type) }
|
33
|
+
|
34
|
+
scope :order_by_datt, lambda { |attr_key_with_order, object_type| order_by_dynamic_attribute(attr_key_with_order, object_type) }
|
35
|
+
scope :order_by_dynamic_attribute, lambda { |attr_key_with_order, object_type|
|
36
|
+
# possible attr_key_with_order forms: "field_name", "field_name ASC", "field_name DESC"
|
37
|
+
split_attr_key_with_order = attr_key_with_order.split(" ")
|
38
|
+
attr_key = split_attr_key_with_order.first
|
39
|
+
order_by = split_attr_key_with_order.last if split_attr_key_with_order.size > 1
|
40
|
+
order_value = "dynamic_attributes.#{object_type}_value"
|
41
|
+
order_value << " #{order_by}" if order_by
|
42
|
+
scope_self.with_dynamic_attribute_key(attr_key).joins(:dynamic_attributes).with_dynamic_attribute_type(object_type).order(order_value)
|
43
|
+
}
|
44
|
+
|
45
|
+
scope :where_datt, lambda { |opts| where_dynamic_attribute(opts) }
|
46
|
+
scope :where_dynamic_attribute, lambda { |opts|
|
47
|
+
# TODO accept stuff other than the normal hash
|
48
|
+
# Lifted from AR::Relation#build_where
|
49
|
+
attributes = case opts
|
50
|
+
when String, Array
|
51
|
+
self.expand_hash_conditions_for_aggregates(opts)
|
52
|
+
when Hash
|
53
|
+
opts
|
54
|
+
end
|
55
|
+
results = self
|
56
|
+
attributes.each do |k, v|
|
57
|
+
conditions = "exists (" +
|
58
|
+
"select 1 from dynamic_attributes dynamic_attribute where " +
|
59
|
+
"#{self.table_name}.id = dynamic_attribute.attributable_id " +
|
60
|
+
"and dynamic_attribute.attributable_type = :attributable_type " +
|
61
|
+
"and dynamic_attribute.attr_key = :attr_key and dynamic_attribute.#{DynamicAttribute.attr_column(v)} = :value" +
|
62
|
+
")"
|
63
|
+
results = results.where(conditions, :attributable_type => self.name, :attr_key => k.to_s, :value => v)
|
64
|
+
end
|
65
|
+
results
|
66
|
+
}
|
67
|
+
|
68
|
+
def self.method_missing(method_id, *arguments)
|
69
|
+
# TODO better way to hook this into the rails code, and not define my own
|
70
|
+
begin # Prioritize ActiveRecord's method_missing
|
71
|
+
super(method_id, *arguments)
|
72
|
+
rescue NoMethodError => e
|
73
|
+
if method_id.to_s =~ /^find_(all_|last_)?by_(dynamic_attribute|datt)_([_a-z]\w*)$/
|
74
|
+
all_or_last = $1
|
75
|
+
attributes = $3.split("_and_")
|
76
|
+
results = self
|
77
|
+
attributes.each_with_index do |attribute, i|
|
78
|
+
results = results.where_dynamic_attribute(attribute.to_sym => arguments[i])
|
79
|
+
end
|
80
|
+
|
81
|
+
case all_or_last
|
82
|
+
when "all_"
|
83
|
+
results
|
84
|
+
when "last_"
|
85
|
+
results.last
|
86
|
+
when nil
|
87
|
+
results.first
|
88
|
+
else
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
else
|
92
|
+
raise e
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Override AR::Base#respond_to? so we can return the matchers even if the
|
98
|
+
# attribute doesn't exist in the actual columns. Is this expensive?
|
99
|
+
def respond_to?(method_id, include_private=false)
|
100
|
+
# TODO perhaps we could save a cache somewhere of all methods used by
|
101
|
+
# any of the records of this class. that way, we can make this method
|
102
|
+
# act a bit more like AR::Base#respond_to?
|
103
|
+
# Ex:
|
104
|
+
# return true if all_attributes_exists?(match.attribute_names) || all_dynamic_attributes_exists?(match.attribute_names)
|
105
|
+
if match = ActiveRecord::DynamicFinderMatch.match(method_id)
|
106
|
+
return true
|
107
|
+
elsif match = ActiveRecord::DynamicScopeMatch.match(method_id)
|
108
|
+
return true
|
109
|
+
end
|
110
|
+
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
alias :has_datts :has_dynamic_attributes
|
116
|
+
end
|
117
|
+
end
|
@@ -2,6 +2,14 @@ class DynamicAttribute < ActiveRecord::Base
|
|
2
2
|
# TODO
|
3
3
|
# Set table name from configuration
|
4
4
|
belongs_to :attributable, :polymorphic => true
|
5
|
+
belongs_to :dynamic_attribute_definition
|
6
|
+
|
7
|
+
# Returns the hash that defined this
|
8
|
+
def definer
|
9
|
+
defining_record = attributable.defining_record
|
10
|
+
return {} if defining_record.nil?
|
11
|
+
defining_record.definition[attr_key.to_sym]
|
12
|
+
end
|
5
13
|
|
6
14
|
def value
|
7
15
|
send("#{object_type}_value")
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class DynamicAttributeDefinition < ActiveRecord::Base
|
2
|
+
belongs_to :attribute_defineable, :polymorphic => true
|
3
|
+
|
4
|
+
# definition should contain a json hash like this:
|
5
|
+
# {"body" => {"object_type" => "text"}}
|
6
|
+
#
|
7
|
+
# If you want more info, you can add other descriptions
|
8
|
+
# {"body" => {"object_type" => "text", "name" => "Body", "description" => "What the body will include"}}
|
9
|
+
serialize :definition
|
10
|
+
end
|
@@ -4,7 +4,7 @@ module DattsRight
|
|
4
4
|
key = name.to_s.underscore
|
5
5
|
return false if self.class.columns_hash[key] # if key already exists as a normal attribute
|
6
6
|
unless dynamic_attribute?(key)
|
7
|
-
new_dynamic_attribute = dynamic_attributes.new :
|
7
|
+
new_dynamic_attribute = dynamic_attributes.new :attr_key => key, :object_type => object_type, "#{object_type}_value".to_sym => value
|
8
8
|
@dynamic_attributes_cache[key.to_sym] = new_dynamic_attribute
|
9
9
|
return new_dynamic_attribute
|
10
10
|
end
|
@@ -12,14 +12,9 @@ module DattsRight
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def add_dynamic_attribute!(name, object_type, value=nil)
|
15
|
+
dynamic_attribute = add_dynamic_attribute(name, object_type, value)
|
16
|
+
dynamic_attribute.save if dynamic_attribute
|
15
17
|
key = name.to_s.underscore
|
16
|
-
return false if self.class.columns_hash[key] # if key already exists as a normal attribute
|
17
|
-
unless dynamic_attribute?(key)
|
18
|
-
new_dynamic_attribute = dynamic_attributes.create! :name => name, :attr_key => key, :object_type => object_type, "#{object_type}_value".to_sym => value
|
19
|
-
@dynamic_attributes_cache[key.to_sym] = new_dynamic_attribute
|
20
|
-
return new_dynamic_attribute
|
21
|
-
end
|
22
|
-
return false
|
23
18
|
end
|
24
19
|
|
25
20
|
def remove_dynamic_attribute(name)
|
@@ -113,6 +108,11 @@ module DattsRight
|
|
113
108
|
assign_multiparameter_attributes(multi_parameter_attributes)
|
114
109
|
end
|
115
110
|
|
111
|
+
def defining_record
|
112
|
+
return nil if dynamic_attributes_options[:of].nil?
|
113
|
+
send dynamic_attributes_options[:of].to_s
|
114
|
+
end
|
115
|
+
|
116
116
|
private
|
117
117
|
|
118
118
|
# Called after validation on update so that dynamic attributes behave
|
@@ -120,19 +120,7 @@ module DattsRight
|
|
120
120
|
# until save is called.
|
121
121
|
def build_dynamic_attributes
|
122
122
|
@dynamic_attributes_cache ||= {}
|
123
|
-
@dynamic_attributes_cache.each
|
124
|
-
#puts "about to save: #{v.inspect}"
|
125
|
-
v.save
|
126
|
-
#puts "Just to saved: #{v.inspect}"
|
127
|
-
#dynamic_attribute = dynamic_attributes.find_by_attr_key(k.to_s)
|
128
|
-
#value_column = "#{v[:object_type]}_value".to_sym
|
129
|
-
#if dynamic_attribute
|
130
|
-
#dynamic_attribute.update_attributes value_column => v[:value], :object_type => v[:object_type]
|
131
|
-
#else
|
132
|
-
##@dynamic_attributes_cache[k] = dynamic_attributes.create! :name => k, :attr_key => k, value_column => v[:value], :object_type => v[:object_type]
|
133
|
-
#@dynamic_attributes_cache[k].save
|
134
|
-
#end
|
135
|
-
end
|
123
|
+
@dynamic_attributes_cache.each { |k, v| v.save }
|
136
124
|
end
|
137
125
|
|
138
126
|
def cache_dynamic_attributes
|
@@ -145,34 +133,38 @@ module DattsRight
|
|
145
133
|
end
|
146
134
|
|
147
135
|
def method_missing(method_id, *args, &body)
|
148
|
-
#begin
|
149
|
-
#method_missing_without_dynamic_attributes method_id, *args, &block
|
150
|
-
#rescue NoMethodError => e
|
151
|
-
#attr_name = method_id.to_s.sub(/\=$/, '')
|
152
|
-
#if is_dynamic_attribute?(attr_name)
|
153
|
-
#if method_id.to_s =~ /\=$/
|
154
|
-
#return write_attribute_with_dynamic_attributes(attr_name, args[0])
|
155
|
-
#else
|
156
|
-
#return read_attribute_with_dynamic_attributes(attr_name)
|
157
|
-
#end
|
158
|
-
#end
|
159
|
-
#raise e
|
160
|
-
#end
|
161
136
|
begin
|
162
137
|
super(method_id, *args, &body)
|
163
138
|
rescue NoMethodError => e
|
164
139
|
attr_name = method_id.to_s.sub(/\=$/, '')
|
165
140
|
if dynamic_attribute?(attr_name)
|
166
|
-
#puts "#{attr_name} is a dynamic_attribute"
|
167
141
|
method_id.to_s =~ /\=$/ ? write_dynamic_attribute(attr_name, args[0]) : read_dynamic_attribute(attr_name)
|
168
|
-
#else
|
169
|
-
#super(method_id, *args, &body) # send it to super again, so that any magic activerecord needs to do will still work
|
170
142
|
else
|
171
143
|
raise NoMethodError
|
172
144
|
end
|
173
145
|
end
|
174
146
|
end
|
175
147
|
|
148
|
+
def save_dynamic_attribute_definition
|
149
|
+
dynamic_attribute_definition.save
|
150
|
+
end
|
151
|
+
|
152
|
+
def inherit_definition
|
153
|
+
#puts "In inherit_definition"
|
154
|
+
#puts "------- #{dynamic_attributes_options[:of]}"
|
155
|
+
#dynamic_attribute_definition.create if dynamic_attributes_options[:definition]
|
156
|
+
if dynamic_attributes_options[:of]
|
157
|
+
#puts "There is a defining record of #{self.class.name}##{self.name} in #{defining_record.inspect}. #{defining_record.dynamic_attribute_definition.inspect}"
|
158
|
+
if defining_record
|
159
|
+
defining_record.definition.each do |k, v|
|
160
|
+
datt = add_dynamic_attribute(k, v[:object_type])
|
161
|
+
datt.dynamic_attribute_definition
|
162
|
+
#puts "Added #{datt.inspect}"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
176
168
|
alias :add_datt :add_dynamic_attribute
|
177
169
|
alias :add_datt! :add_dynamic_attribute!
|
178
170
|
alias :remove_datt :remove_dynamic_attribute
|
data/lib/datts_right/page.rb
CHANGED
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe DynamicAttributeDefinition do
|
4
|
+
it "should serialize the definition" do
|
5
|
+
category = Category.create
|
6
|
+
category.definition = {:that => "is", :cool => "right?"}
|
7
|
+
category.definition[:that].should == "is"
|
8
|
+
category.definition[:cool].should == "right?"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe DattsRight, "when inheriting the definition of an association" do
|
4
|
+
before do
|
5
|
+
@category = Category.create
|
6
|
+
@category.definition = {:teaser => {:object_type => "text"}, :price => {:object_type => "integer"}}
|
7
|
+
@category.save
|
8
|
+
@page = Page.create :category => @category
|
9
|
+
end
|
10
|
+
it "should create the dynamic attributes" do
|
11
|
+
@page.teaser = "hi"
|
12
|
+
@page.teaser.should == "hi"
|
13
|
+
@page.price = 200
|
14
|
+
@page.price.should == 200
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should point to the dynamic attribute definition" do
|
18
|
+
@page.datt_details(:teaser).definer.should == {:object_type => "text"}
|
19
|
+
@page.datt_details(:price).definer.should == {:object_type => "integer"}
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -6,6 +6,7 @@ require 'active_record/errors'
|
|
6
6
|
require 'rspec'
|
7
7
|
require 'datts_right'
|
8
8
|
require 'datts_right/page'
|
9
|
+
require 'datts_right/category'
|
9
10
|
|
10
11
|
# Allow to connect to SQLite
|
11
12
|
root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
@@ -24,14 +25,15 @@ RSpec.configure do |config|
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def reset_database
|
27
|
-
%W(pages dynamic_attributes).each do |table_name|
|
28
|
+
%W(pages categories dynamic_attributes dynamic_attribute_definitions).each do |table_name|
|
28
29
|
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS '#{table_name}'")
|
29
30
|
end
|
30
31
|
ActiveRecord::Base.connection.create_table(:pages) do |t|
|
31
32
|
t.string :name, :default => "My name"
|
33
|
+
t.integer :category_id
|
32
34
|
end
|
33
35
|
ActiveRecord::Base.connection.create_table(:dynamic_attributes) do |t|
|
34
|
-
t.
|
36
|
+
t.integer :dynamic_attribute_definition_id
|
35
37
|
t.string :attr_key, :null => false
|
36
38
|
t.string :object_type, :null => false
|
37
39
|
t.string :attributable_type, :null => false
|
@@ -40,5 +42,13 @@ def reset_database
|
|
40
42
|
t.send(type, "#{type}_value".to_sym)
|
41
43
|
end
|
42
44
|
end
|
45
|
+
ActiveRecord::Base.connection.create_table(:dynamic_attribute_definitions) do |t|
|
46
|
+
t.text :definition
|
47
|
+
t.string :attribute_defineable_type, :null => false
|
48
|
+
t.integer :attribute_defineable_id, :null => false
|
49
|
+
end
|
50
|
+
ActiveRecord::Base.connection.create_table(:categories) do |t|
|
51
|
+
t.string :name
|
52
|
+
end
|
43
53
|
end
|
44
54
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: datts_right
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 63
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 16
|
10
|
+
version: 0.0.16
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ramon Tayag
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-04-
|
18
|
+
date: 2011-04-19 00:00:00 +08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -2584,6 +2584,96 @@ dependencies:
|
|
2584
2584
|
name: rcov
|
2585
2585
|
version_requirements: *id171
|
2586
2586
|
prerelease: false
|
2587
|
+
- !ruby/object:Gem::Dependency
|
2588
|
+
type: :development
|
2589
|
+
requirement: &id172 !ruby/object:Gem::Requirement
|
2590
|
+
none: false
|
2591
|
+
requirements:
|
2592
|
+
- - ">="
|
2593
|
+
- !ruby/object:Gem::Version
|
2594
|
+
hash: 3
|
2595
|
+
segments:
|
2596
|
+
- 0
|
2597
|
+
version: "0"
|
2598
|
+
name: autotest
|
2599
|
+
version_requirements: *id172
|
2600
|
+
prerelease: false
|
2601
|
+
- !ruby/object:Gem::Dependency
|
2602
|
+
type: :development
|
2603
|
+
requirement: &id173 !ruby/object:Gem::Requirement
|
2604
|
+
none: false
|
2605
|
+
requirements:
|
2606
|
+
- - ">="
|
2607
|
+
- !ruby/object:Gem::Version
|
2608
|
+
hash: 3
|
2609
|
+
segments:
|
2610
|
+
- 0
|
2611
|
+
version: "0"
|
2612
|
+
name: sqlite3
|
2613
|
+
version_requirements: *id173
|
2614
|
+
prerelease: false
|
2615
|
+
- !ruby/object:Gem::Dependency
|
2616
|
+
type: :development
|
2617
|
+
requirement: &id174 !ruby/object:Gem::Requirement
|
2618
|
+
none: false
|
2619
|
+
requirements:
|
2620
|
+
- - ~>
|
2621
|
+
- !ruby/object:Gem::Version
|
2622
|
+
hash: 3
|
2623
|
+
segments:
|
2624
|
+
- 2
|
2625
|
+
- 3
|
2626
|
+
- 0
|
2627
|
+
version: 2.3.0
|
2628
|
+
name: rspec
|
2629
|
+
version_requirements: *id174
|
2630
|
+
prerelease: false
|
2631
|
+
- !ruby/object:Gem::Dependency
|
2632
|
+
type: :development
|
2633
|
+
requirement: &id175 !ruby/object:Gem::Requirement
|
2634
|
+
none: false
|
2635
|
+
requirements:
|
2636
|
+
- - ~>
|
2637
|
+
- !ruby/object:Gem::Version
|
2638
|
+
hash: 23
|
2639
|
+
segments:
|
2640
|
+
- 1
|
2641
|
+
- 0
|
2642
|
+
- 0
|
2643
|
+
version: 1.0.0
|
2644
|
+
name: bundler
|
2645
|
+
version_requirements: *id175
|
2646
|
+
prerelease: false
|
2647
|
+
- !ruby/object:Gem::Dependency
|
2648
|
+
type: :development
|
2649
|
+
requirement: &id176 !ruby/object:Gem::Requirement
|
2650
|
+
none: false
|
2651
|
+
requirements:
|
2652
|
+
- - ~>
|
2653
|
+
- !ruby/object:Gem::Version
|
2654
|
+
hash: 7
|
2655
|
+
segments:
|
2656
|
+
- 1
|
2657
|
+
- 5
|
2658
|
+
- 2
|
2659
|
+
version: 1.5.2
|
2660
|
+
name: jeweler
|
2661
|
+
version_requirements: *id176
|
2662
|
+
prerelease: false
|
2663
|
+
- !ruby/object:Gem::Dependency
|
2664
|
+
type: :development
|
2665
|
+
requirement: &id177 !ruby/object:Gem::Requirement
|
2666
|
+
none: false
|
2667
|
+
requirements:
|
2668
|
+
- - ">="
|
2669
|
+
- !ruby/object:Gem::Version
|
2670
|
+
hash: 3
|
2671
|
+
segments:
|
2672
|
+
- 0
|
2673
|
+
version: "0"
|
2674
|
+
name: rcov
|
2675
|
+
version_requirements: *id177
|
2676
|
+
prerelease: false
|
2587
2677
|
description: Creates a separate table that saves all your dynamic attributes.
|
2588
2678
|
email: ramon@tayag.net
|
2589
2679
|
executables: []
|
@@ -2606,7 +2696,10 @@ files:
|
|
2606
2696
|
- VERSION
|
2607
2697
|
- datts_right.gemspec
|
2608
2698
|
- lib/datts_right.rb
|
2699
|
+
- lib/datts_right/base.rb
|
2700
|
+
- lib/datts_right/category.rb
|
2609
2701
|
- lib/datts_right/dynamic_attribute.rb
|
2702
|
+
- lib/datts_right/dynamic_attribute_definition.rb
|
2610
2703
|
- lib/datts_right/exceptions.rb
|
2611
2704
|
- lib/datts_right/instance_methods.rb
|
2612
2705
|
- lib/datts_right/page.rb
|
@@ -2614,6 +2707,8 @@ files:
|
|
2614
2707
|
- spec/datt_spec.rb
|
2615
2708
|
- spec/datts_right/add_dynamic_attributes_spec.rb
|
2616
2709
|
- spec/datts_right/attributes_spec.rb
|
2710
|
+
- spec/datts_right/dynamic_attribute_definition_spec.rb
|
2711
|
+
- spec/datts_right/inheritance_spec.rb
|
2617
2712
|
- spec/datts_right/remove_dynamic_attribute_spec.rb
|
2618
2713
|
- spec/datts_right_spec.rb
|
2619
2714
|
- spec/spec_helper.rb
|
@@ -2655,6 +2750,8 @@ test_files:
|
|
2655
2750
|
- spec/datt_spec.rb
|
2656
2751
|
- spec/datts_right/add_dynamic_attributes_spec.rb
|
2657
2752
|
- spec/datts_right/attributes_spec.rb
|
2753
|
+
- spec/datts_right/dynamic_attribute_definition_spec.rb
|
2754
|
+
- spec/datts_right/inheritance_spec.rb
|
2658
2755
|
- spec/datts_right/remove_dynamic_attribute_spec.rb
|
2659
2756
|
- spec/datts_right_spec.rb
|
2660
2757
|
- spec/spec_helper.rb
|