acts_as_tableless 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,10 +2,87 @@
2
2
 
3
3
  Create tableless activerecord objects that support associations and validations.
4
4
 
5
- 1. Install gem
6
- 2. Add <tt>acts_as_tableless</tt> to a activerecord model
5
+ == Install:
7
6
 
8
- Note:
7
+ 1. add <tt>gem 'acts_as_tableless'</tt> to your gemfile
8
+ 2. run <tt>bundle install</tt>
9
+ 3. add <tt>acts_as_tableless</tt> to a activerecord model
10
+
11
+ == Usage:
12
+
13
+ class User < ActiveRecord::Base
14
+ has_many :shapes
15
+ has_many :user_colors
16
+ has_many :colors, :through => :user_colors
17
+ has_one :size
18
+ belongs_to :number
19
+ attr_accessible :name, :number_id, :letter_id
20
+ end
21
+
22
+ class Color < ActiveRecord::Base
23
+ acts_as_tableless
24
+ column :id, :integer
25
+ column :name, :string
26
+ attr_accessible :id, :name
27
+ belongs_to :user_colors
28
+ end
29
+
30
+ class Letter < ActiveRecord::Base
31
+ acts_as_tableless
32
+ column :id, :integer
33
+ column :name, :string
34
+ column :user_id, :integer
35
+ attr_accessible :id, :name, :user_id
36
+ has_and_belongs_to_many :users
37
+ end
38
+
39
+ class Number < ActiveRecord::Base
40
+ acts_as_tableless
41
+ column :id, :integer
42
+ column :name, :string
43
+ attr_accessible :id, :name
44
+ has_many :users
45
+ end
46
+
47
+ class Shape < ActiveRecord::Base
48
+ acts_as_tableless
49
+ column :id, :integer
50
+ column :name, :string
51
+ column :user_id, :integer
52
+ attr_accessible :id, :name, :user_id
53
+ belongs_to :user
54
+ end
55
+
56
+ class Size < ActiveRecord::Base
57
+ acts_as_tableless
58
+ column :id, :integer
59
+ column :name, :string
60
+ column :user_id, :integer
61
+ attr_accessible :id, :name, :user_id
62
+ belongs_to :user
63
+ end
64
+
65
+ class UserColor < ActiveRecord::Base
66
+ belongs_to :user
67
+ belongs_to :color
68
+ attr_accessible :user_id, :color_id
69
+ end
70
+
71
+ == Examples:
72
+
73
+ @user = User.create(:name => "User")
74
+
75
+ @user.shapes.create(:name => "Octagon") #=> #<Shape id: 1, name: "Octagon", user_id: 1>
76
+
77
+ @user.colors << Color.create([{:name => "Green"},{:name => "Blue"}]) #=> [#<Color id: 1, name: "Green">, #<Color id: 2, name: "Blue">]
78
+
79
+ @user.size.create(:name => "Large") #=> #<Size id: 1, name: "Large", user_id: 1>
80
+
81
+ @user.number = Number.create(name: "One") #=> #<Number id: 1, name: "One">
82
+
83
+ More stuff in the test file.
84
+
85
+ == Notes:
9
86
 
10
87
  This gem relies on String#singularize which is known to have some problems. To correct errors such as:
11
88
 
@@ -18,4 +95,4 @@ please add something similar to:
18
95
  inflect.singular("status", "status")
19
96
  end
20
97
 
21
- to your environment.rb file before the <tt>Application.initialize!</tt> line
98
+ to your environment.rb file before the <tt>Application.initialize!</tt> line
@@ -37,8 +37,25 @@ module ActsAsTableless
37
37
  def all
38
38
  ActsAsTableless.class_variable_get(:"@@#{self.name.underscore}")
39
39
  end
40
+
41
+ def find(id)
42
+ ActsAsTableless.class_variable_get(:"@@#{self.name.underscore}").select{|record| record.id == id}.first
43
+ end
44
+
45
+ def delete(ids)
46
+ ids = [ids] unless ids.is_a?(Array)
47
+ # this coule be improved
48
+ ids.each do |id|
49
+ find(id).delete
50
+ end
51
+ end
52
+
53
+ def exists?(id)
54
+ find(id).nil? ? false : true
55
+ end
40
56
  end
41
57
  ActsAsTableless.class_variable_set(:"@@#{self.name.underscore}", [])
58
+ ActsAsTableless.class_variable_set(:"@@#{self.name.underscore}_increment", 1)
42
59
  include InstanceMethods
43
60
  end
44
61
  end
@@ -47,18 +64,31 @@ module ActsAsTableless
47
64
  def persisted?
48
65
  false
49
66
  end
50
-
67
+
51
68
  def readonly?
52
- false
69
+ true
53
70
  end
54
71
 
55
72
  def save(validate = true)
73
+ unless self.id
74
+ id = ActsAsTableless.class_variable_get(:"@@#{self.class.name.underscore}_increment")
75
+ id += 1 while self.class.send(:find, id)
76
+ self.id = ActsAsTableless.class_variable_set(:"@@#{self.class.name.underscore}_increment", id)
77
+ end
78
+ raise "Duplicate ID" if self.class.send(:find, id) # these are read only
56
79
  if validate ? valid? : true
57
- ActsAsTableless.class.class_variable_set(:"@@#{self.class.name.underscore}", ActsAsTableless.class_variable_get(:"@@#{self.class.name.underscore}").push(self))
80
+ ActsAsTableless.class_variable_get(:"@@#{self.class.name.underscore}").push(self)
81
+ return self
58
82
  end
59
83
  end
60
84
 
85
+ def delete
86
+ ActsAsTableless.class_variable_get(:"@@#{self.class.name.underscore}").delete(self)
87
+ return true
88
+ end
89
+
61
90
  alias :save! :save
91
+ alias :delete! :delete
62
92
  end
63
93
  end
64
94
 
@@ -84,7 +114,8 @@ module ActiveRecord
84
114
  []
85
115
  else
86
116
  through_objects = [through_objects] unless through_objects.is_a?(Array)
87
- through_objects.collect{|object| association_class.find(object.send("#{association_class.name.underscore}_id")) }
117
+ through_association_id = [:has_one, :belongs_to].include?( options[:through].to_s.singularize.camelize.constantize.reflect_on_all_associations.select{ |associations| associations.name.to_s == class_variable_name }.first.macro ) ? association_id.to_s.singularize.to_sym : association_id
118
+ through_objects.collect{|object| object.send(through_association_id) }.flatten
88
119
  end
89
120
 
90
121
  records.instance_variable_set(:@parent, self)
@@ -96,7 +127,7 @@ module ActiveRecord
96
127
  when :has_many
97
128
  associated_records.each do |associated_record|
98
129
  raise ActiveRecord::AssociationTypeMismatch, "#{@association_class.name} expected, got #{associated_record.inspect}" unless @association_class.name == associated_record.class.name
99
- @parent.send(@options[:through]).send(:new, "#{@association_class.name.underscore}_id".to_sym => associated_record.id)
130
+ @options[:through].to_s.singularize.camelize.constantize.create("#{@parent.class.name.underscore}_id".to_sym => @parent.id, "#{@association_class.name.underscore}_id".to_sym => associated_record.id)
100
131
  end
101
132
  when :has_one
102
133
  # not yet implemented
@@ -108,23 +139,94 @@ module ActiveRecord
108
139
  # not yet implemented
109
140
  []
110
141
  end
111
- @parent.roles
142
+ self
112
143
  end
113
- # def records.create(attributes = nil, options = {})
114
- # if attributes.is_a?(Array)
115
- # attributes.collect { |record_attributes| create(record_attributes, options) }
116
- # else
117
- # record = new(attributes, options)
118
- # record.save
119
- # record
120
- # end
121
- # end
122
-
144
+ def records.create(new_records_attributes = nil, options = {})
145
+ new_records = new_records_attributes.is_a?(Array) ? [] : nil
146
+ new_records_attributes = [new_records_attributes] unless new_records_attributes.is_a?(Array)
147
+ case @parent.class.reflect_on_all_associations.select{|association| association.name == @options[:through]}.first.macro
148
+ when :has_many
149
+ new_records_attributes = [new_records_attributes] unless new_records_attributes.is_a?(Array)
150
+ new_records = []
151
+ new_records_attributes.each do |attributes|
152
+ new_record = @association_class.create(attributes)
153
+ @options[:through].to_s.singularize.camelize.constantize.create("#{@parent.class.name.underscore}_id".to_sym => @parent.id, "#{@association_class.name.underscore}_id".to_sym => new_record.id)
154
+ if new_records.is_a?(Array)
155
+ new_records << new_record
156
+ else
157
+ return new_record
158
+ end
159
+ end
160
+ when :has_one
161
+ # not yet implemented
162
+ []
163
+ when :belongs_to
164
+ # not yet implemented
165
+ []
166
+ when :has_and_belongs_to_many
167
+ # not yet implemented
168
+ []
169
+ end
170
+ return new_records
171
+ end
172
+
123
173
  return records
124
174
  end
175
+
176
+ define_method("#{association_id.to_s}=") do |associated_records|
177
+ new_associated_records = associated_records.is_a?(Array) ? [] : nil
178
+ associated_records = [associated_records] unless associated_records.is_a?(Array)
179
+ through_class = options[:through].to_s.singularize.camelize.constantize
180
+ through_class.all.select{|r|r.send("#{self.class.name.underscore}_id") == self.id}.each{|r|r.delete} # this line could be made more efficient
181
+ case self.class.reflect_on_all_associations.select{|association| association.name == options[:through]}.first.macro
182
+ when :has_many
183
+ associated_records.each do |associated_record|
184
+ new_associated_record = through_class.new
185
+ new_associated_record.send("#{self.class.name.underscore}_id=", self.id)
186
+ new_associated_record.send("#{association_class.name.underscore}_id=", associated_record.id)
187
+ new_associated_record.save
188
+ if new_associated_records.is_a?(Array)
189
+ new_associated_records << new_associated_record
190
+ else
191
+ return new_associated_record
192
+ end
193
+ end
194
+ when :has_one
195
+ # not yet implemented
196
+ []
197
+ when :belongs_to
198
+ # not yet implemented
199
+ []
200
+ when :has_and_belongs_to_many
201
+ # not yet implemented
202
+ []
203
+ end
204
+ return new_associated_records
205
+ end
206
+
125
207
  else
126
208
  define_method(association_id.to_s) do
127
- association_class.all.select{|record| record.send("#{self.class.name.underscore}_id") == self.id}
209
+ records = association_class.all.select{|record| record.send("#{self.class.name.underscore}_id") == self.id}
210
+
211
+ records.instance_variable_set(:@parent, self)
212
+ records.instance_variable_set(:@association_class, association_class)
213
+ def records.create(new_records_attributes = nil)
214
+ new_records = new_records_attributes.is_a?(Array) ? [] : nil
215
+ new_records_attributes = [new_records_attributes] unless new_records_attributes.is_a?(Array)
216
+ new_records_attributes.each do |attributes|
217
+ new_record = @association_class.new(attributes)
218
+ new_record.send("#{@parent.class.name.underscore}_id=", @parent.id)
219
+ new_record.save
220
+ if new_records.is_a?(Array)
221
+ new_records << new_record
222
+ else
223
+ return new_record
224
+ end
225
+ end
226
+ return new_records
227
+ end
228
+
229
+ return records
128
230
  end
129
231
  end
130
232
  end
@@ -143,7 +245,18 @@ module ActiveRecord
143
245
  end
144
246
  else
145
247
  define_method(association_id.to_s) do
146
- association_class.all.select{|record| record.send("#{self.class.name.underscore}_id") == self.id}.first
248
+ record = association_class.all.select{|r|r.send("#{self.class.name.underscore}_id") == self.id}.first
249
+ record.instance_variable_set(:@parent, self)
250
+ record.instance_variable_set(:@association_class, association_class)
251
+ def record.create(attributes)
252
+ old_record = @parent.send(@association_class.name.underscore)
253
+ new_record = @association_class.new(attributes)
254
+ new_record.send("#{@parent.class.name.underscore}_id=", @parent.id)
255
+ new_record.save
256
+ old_record.delete unless old_record.nil?
257
+ return new_record
258
+ end
259
+ return record
147
260
  end
148
261
  end
149
262
  end
@@ -155,7 +268,7 @@ module ActiveRecord
155
268
  if class_variable_name.camelize.constantize.send(:included_modules).include?(ActsAsTableless) # || ActsAsTableless.class_variables.include?(:"@@#{class_variable_name}")
156
269
  association_class = class_variable_name.camelize.constantize rescue nil
157
270
  define_method(association_id.to_s) do
158
- association_class.find(self.send("#{association_class.name.underscore}_id"))
271
+ association_class.all.select{|record| record.id == self.send("#{association_class.name.underscore}_id")}.first
159
272
  end
160
273
  end
161
274
  end
@@ -165,8 +278,10 @@ module ActiveRecord
165
278
  class_variable_name = association_id.to_s.singularize
166
279
  if class_variable_name.camelize.constantize.send(:included_modules).include?(ActsAsTableless) # || ActsAsTableless.class_variables.include?(:"@@#{class_variable_name}")
167
280
  association_class = class_variable_name.camelize.constantize rescue nil
168
- # not yet implemented
169
- []
281
+ # not yet implemented, and may never be; use has_many
282
+ define_method(association_id.to_s) do
283
+ []
284
+ end
170
285
  end
171
286
  end
172
287
  end
@@ -1,3 +1,3 @@
1
1
  module ActsAsTableless
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -2,11 +2,14 @@ require 'test_helper'
2
2
 
3
3
  class ActsAsTablelessTest < ActiveSupport::TestCase
4
4
  def setup
5
- build_tableless_objects
5
+ build_users
6
+
7
+ @user = @user_one
6
8
  end
7
9
 
8
- test "acts_as_tableless_active_record_methods" do
9
- tabless_models = [HasManyTablelessObject]
10
+ test "active_record_methods" do
11
+ build_all
12
+ tabless_models = [Color, Letter, Number, Shape, Size]
10
13
 
11
14
  tabless_models.each do |tabless_model|
12
15
  assert tabless_model.table_name
@@ -19,47 +22,172 @@ class ActsAsTablelessTest < ActiveSupport::TestCase
19
22
 
20
23
  tableless_object = tabless_model.all.select{|object|object.id == 1}.first
21
24
  assert !tableless_object.persisted?
22
- assert !tableless_object.readonly?
23
- assert_equal "default", tableless_object.test
24
- tableless_object.test = "update"
25
- tableless_object.save!
26
- assert_equal "update", tableless_object.test
25
+ assert tableless_object.readonly?
26
+ end
27
+ end
28
+
29
+ test "has_many" do
30
+ # has_many :shapes
31
+ build_shapes
32
+ assert @user.present?
33
+ assert @user.shapes.present?
34
+ assert_equal 2, @user.shapes.count
35
+ assert ["Circle", "Square"].sort.eql?(@user.shapes.collect(&:name).sort)
36
+
37
+ @user.shapes.create(:name => "Octagon")
38
+ assert_equal 3, @user.shapes.count
39
+ assert ["Circle", "Octagon", "Square"].sort.eql?(@user.shapes.collect(&:name).sort)
40
+ end
41
+
42
+ test "has_many_through" do
43
+ # has_many :user_colors
44
+ # has_many :colors, :through => :user_colors
45
+ build_colors
46
+ build_user_colors
47
+ assert @user
48
+ assert @user.user_colors
49
+ assert @user.colors
50
+ assert_equal 3, @user.colors.count
51
+ assert ["Blue", "Purple", "Red"].sort.eql?(@user.colors.collect(&:name).sort)
52
+
53
+ @user.colors << Color.all.select{|c|c.name == "Green"}
54
+ assert ["Blue", "Green", "Purple", "Red"].sort.eql?(@user.colors.collect(&:name).sort)
55
+
56
+ @user.colors.create(:name => "Orange")
57
+ assert ["Blue", "Green", "Orange", "Purple", "Red"].sort.eql?(@user.colors.collect(&:name).sort)
58
+
59
+ @user.colors = Color.all.select{|c| ["Yellow", "Blue"].include?(c.name)}
60
+ assert ["Blue", "Yellow"].sort.eql?(@user.colors.collect(&:name).sort)
61
+ end
62
+
63
+ test "has_one_association" do
64
+ # has_one :size
65
+ build_sizes
66
+ assert @user
67
+ assert @user.size
68
+ assert_equal "Small", @user.size.name
69
+ assert Size.all.collect(&:name).include?("Small")
70
+ assert !Size.all.collect(&:name).include?("X-Large")
71
+
72
+ @user.size.create(:name => "X-Large")
73
+ assert_equal "X-Large", @user.size.name
74
+ assert Size.all.collect(&:name).include?("X-Large")
75
+ assert !Size.all.collect(&:name).include?("Small")
76
+ end
77
+
78
+ test "belongs_to_association" do
79
+ # belongs_to :number
80
+ build_numbers
81
+ assert @user
82
+ assert @user.number
83
+ assert_equal "One", @user.number.name
84
+
85
+ @user.number = @@two
86
+ @user.save
87
+ assert_equal "Two", @user.number.name
88
+ end
89
+
90
+ # this is not yet implemented, and may never be; use has_many
91
+ test "has_and_belongs_to_many_association" do
92
+ # has_and_belongs_to_many :letters
93
+ build_letters
94
+ assert @user
95
+ assert @user.letters
96
+ # assert_equal 2, @user.letters.count
97
+ # assert ["A", "O"].sort.eql? @user.letters.collect(&:name).sort
98
+
99
+ # @user.letters.create(:name => "Z")
100
+ # assert_equal 3, @user.letters.count
101
+ # assert ["A", "O", "Z"].sort.eql?(@user.letters.collect(&:name).sort)
102
+ end
103
+
104
+ def build_all
105
+ build_colors
106
+ build_letters
107
+ build_numbers
108
+ build_shapes
109
+ build_sizes
110
+ build_users
111
+ build_user_colors
112
+ end
113
+
114
+ def build_colors
115
+ if Color.all.none?
116
+ @@red = Color.create(name: "Red")
117
+ @@yellow = Color.create(name: "Yellow")
118
+ @@blue = Color.create(name: "Blue")
119
+ @@green = Color.create(name: "Green")
120
+ @@purple = Color.create(name: "Purple")
27
121
  end
28
122
  end
29
123
 
30
- test "acts_as_tableless_active_record_association_has_many" do
31
- assert_equal HasManyTablelessObject.all.select{|o|o.active_record_modle_with_association_id = @active_record_modle_with_association.id}.count, @active_record_modle_with_association.has_many_tableless_objects.count
124
+ def build_letters
125
+ if Letter.all.none?
126
+ build_users
127
+ @@a = Letter.create(name: "A", user_id: @user_one.id)
128
+ @@e = Letter.create(name: "E", user_id: @user_two.id)
129
+ @@i = Letter.create(name: "I", user_id: @user_three.id)
130
+ @@o = Letter.create(name: "O", user_id: @user_one.id)
131
+ @@u = Letter.create(name: "U", user_id: @user_two.id)
132
+ @@y = Letter.create(name: "Y", user_id: @user_three.id)
133
+ end
32
134
  end
33
135
 
34
- test "acts_as_tableless_active_record_association_has_one" do
35
- # not yet tested
36
- assert true
136
+ def build_numbers
137
+ if Number.all.none?
138
+ @@one = Number.create(name: "One")
139
+ @@two = Number.create(name: "Two")
140
+ @@three = Number.create(name: "Three")
141
+ @@four = Number.create(name: "Four")
142
+ @@five = Number.create(name: "Five")
143
+ @@six = Number.create(name: "Six")
144
+ @@seven = Number.create(name: "Seven")
145
+ @@eight = Number.create(name: "Eight")
146
+ @@nine = Number.create(name: "Nine")
147
+ @@zero = Number.create(name: "Zero")
148
+ end
37
149
  end
38
150
 
39
- test "acts_as_tableless_active_record_association_belongs_to" do
40
- # not yet tested
41
- assert true
151
+ def build_shapes
152
+ if Shape.all.none?
153
+ build_users
154
+ @@square = Shape.create(name: "Square", user_id: @user_one.id)
155
+ @@rectangle = Shape.create(name: "Rectangle", user_id: @user_two.id)
156
+ @@triangle = Shape.create(name: "Triangle", user_id: @user_three.id)
157
+ @@circle = Shape.create(name: "Circle", user_id: @user_one.id)
158
+ @@hexagon = Shape.create(name: "Hexagon", user_id: @user_two.id)
159
+ end
42
160
  end
43
161
 
44
- test "acts_as_tableless_active_record_association_has_and_belongs_to_many" do
45
- # not yet tested
46
- assert true
162
+ def build_sizes
163
+ if Size.all.none?
164
+ build_users
165
+ @@small = Size.create(name: "Small", user_id: @user_one.id)
166
+ @@medium = Size.create(name: "Meduim", user_id: @user_two.id)
167
+ @@large = Size.create(name: "Large", user_id: @user_three.id)
168
+ end
47
169
  end
48
170
 
49
- test "acts_as_tableless_active_record_validations" do
50
- # not yet tested
51
- assert true
171
+ def build_users
172
+ if User.all.none?
173
+ build_numbers
174
+ @user_one = User.create(name: "User One", number_id: @@one.id)
175
+ @user_two = User.create(name: "User Two", number_id: @@two.id)
176
+ @user_three = User.create(name: "User Three", number_id: @@three.id)
177
+ end
52
178
  end
53
179
 
54
- def build_tableless_objects
55
- @active_record_modle_with_association = ActiveRecordModleWithAssociation.create(id: 1, name: 'default')
56
-
57
- HasManyTablelessObject.create([
58
- {id: 1, test: 'default', active_record_modle_with_association_id: @active_record_modle_with_association.id},
59
- {id: 2, test: 'default', active_record_modle_with_association_id: @active_record_modle_with_association.id},
60
- {id: 3, test: 'default', active_record_modle_with_association_id: @active_record_modle_with_association.id},
61
- {id: 4, test: 'default', active_record_modle_with_association_id: @active_record_modle_with_association.id},
62
- {id: 5, test: 'default', active_record_modle_with_association_id: @active_record_modle_with_association.id},
63
- ])
180
+ def build_user_colors
181
+ if UserColor.all.none?
182
+ build_users
183
+ build_colors
184
+ UserColor.create([
185
+ {user_id: @user_one.id, color_id: @@red.id},
186
+ {user_id: @user_two.id, color_id: @@yellow.id},
187
+ {user_id: @user_one.id, color_id: @@blue.id},
188
+ {user_id: @user_two.id, color_id: @@green.id},
189
+ {user_id: @user_one.id, color_id: @@purple.id},
190
+ ])
191
+ end
64
192
  end
65
193
  end