datts_right 0.0.15 → 0.0.16

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- datts_right (0.0.14)
4
+ datts_right (0.0.15)
5
5
  datts_right
6
6
  rails (>= 3.0.0)
7
7
 
data/README.textile CHANGED
@@ -20,7 +20,7 @@ h2. Installation
20
20
  Create a migration:
21
21
 
22
22
  <pre>
23
- class CreateDatts < ActiveRecord::Migration
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.dynamic_columns
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.15
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.15"
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-18}
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
- module DattsRight
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
@@ -0,0 +1,4 @@
1
+ class Category < ActiveRecord::Base
2
+ has_dynamic_attributes :definition => true
3
+ has_many :pages
4
+ 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 :name => name, :attr_key => key, :object_type => object_type, "#{object_type}_value".to_sym => value
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 do |k, v|
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
@@ -1,3 +1,4 @@
1
1
  class Page < ActiveRecord::Base
2
- has_dynamic_attributes
2
+ belongs_to :category
3
+ has_dynamic_attributes :of => :category
3
4
  end
@@ -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.string :name, :null => false
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: 1
4
+ hash: 63
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 15
10
- version: 0.0.15
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 00:00:00 +08:00
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