acts_has_many 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -33,9 +33,9 @@ end
33
33
  # OR
34
34
 
35
35
  class Company < ActiveRecord::Base
36
- acts_has_many :users # list necessary relations
37
-
38
36
  has_many :users
37
+
38
+ acts_has_many :users # list necessary relations
39
39
  end
40
40
 
41
41
  # acts_has_many options:
@@ -60,13 +60,12 @@ company.actuale? :users # => false ( exclude 1 record of current relation)
60
60
 
61
61
  company # => <Company id: 1, title: "Microsoft">
62
62
 
63
- update_id, delete_id = company.has_many_update({ title: 'Google'}, :users)
63
+ update_record, delete_record = company.has_many_update({ title: 'Google'}, :users)
64
64
  # or
65
- # update_id, delete_id = company.update_with_users({ title: 'Google'})
65
+ # update_record, delete_record = company.update_with_users({ title: 'Google'})
66
66
 
67
- update_id # => 1
68
- delete_id # => nil
69
- company # => <Company id: 1, title: "Google">
67
+ update_record # => <Company id: 1, title: "Google">
68
+ delete_record # => nil
70
69
 
71
70
  user2 = User.create do |user|
72
71
  user.company = company
@@ -80,11 +79,11 @@ company.actuale? :users # => true
80
79
  # user2.destroy # => user will be destroyed, company will rollback (because company is used by other user)
81
80
 
82
81
  company # => <Company id: 1, title: "Google">
83
- update_id, delete_id = company.has_many_update({ title: 'Apple'}, :users)
84
- update_id # => 2
85
- delete_id # => nil
82
+ update_record, delete_id = company.has_many_update({ title: 'Apple'}, :users)
83
+ update_record # => <Company id: 2, title: "Apple">
84
+ delete_record # => nil
86
85
 
87
- user2.company = Company.find(update_id)
86
+ user2.company = update_record
88
87
  user2.save
89
88
 
90
89
  # if you want to destroy user now
@@ -95,65 +94,17 @@ Company.all # [#<Company id: 1, title: "Google">, #<Company id: 2, title: "Apple
95
94
  company.destroy # => false (because company is used)
96
95
 
97
96
  companu # => <Company id: 1, title: "Google">
98
- update_id, delete_id = company.has_many_update({ title: 'Apple'}, :users)
99
- update_id # => 2
100
- delete_id # => 1
97
+ update_record, delete_record = company.has_many_update({ title: 'Apple'}, :users)
98
+ update_record # => <Company id: 2, title: "Apple">
99
+ delete_record # => <Company id: 1, title: "Google">
101
100
 
102
- user2.company = Company.find(update_id)
101
+ user2.company = update_record
103
102
  user2.save
104
103
 
105
104
  company.destroy # => true
106
105
 
107
- # this situation with delete_id best way is "company.delete" because you miss unnecessary check actuality
108
-
109
- # if you need update element and you don't care where it uses you can skip relation
110
- Company.first.has_many_update({title: "IBM"})
111
- # in this case check all relation but if you use this gem you don't have unused record
112
- # that why this exampl is equal Company.first_or_create({title: "IBM"})
113
-
106
+ # this situation with delete_record best way is "company.delete" because you miss unnecessary check actuality
114
107
 
115
- ```
116
- ### Also you can use has_many_update!
117
- ```ruby
118
-
119
- user = User.first
120
- user.company.has_many_update!({title: "Google"}, user)
121
-
122
- user.reload # becaus variable user have old data
123
- user.company # <Company id: 3, title: "Google">
124
- # you don't need update user
125
- ```
126
- ### Use acts_has_many_for with acts_has_many
127
- When use acts_has_many_for you don't need set relation and give parent record in case with has_many_update!
128
- ```ruby
129
-
130
- class User < ActiveRecord::Base
131
- belongs_to :company, dependent: :destroy
132
- acts_has_many_for :company
133
- end
134
-
135
- class Company < ActiveRecord::Base
136
- has_many :users
137
-
138
- acts_has_many # after necessary relation
139
- end
140
-
141
- # in this case
142
-
143
- user = User.first
144
- company = user.company
145
- company.has_many_update!({title: "Google"}) # don't give record user
146
-
147
- user.reload # becaus variable user have old data
148
- user.company # <Company id: 3, title: "Google">
149
-
150
- new_id, del_id = user.company.has_many_update({title: "Google"}) # don't give relation
151
-
152
- # in this case has_many_update doesn't know about relation and the same as if you skip relation(see above)
153
- new_id, del_id = Company.first.has_many_update({title: "something else"})
154
-
155
- # not work becous has_many_update! doesn't know about parent record
156
- Company.first.has_many_update!({title: "something else"})
157
108
  ```
158
109
 
159
110
  ### has_many_update_through used with has_many :through and get array parameters
@@ -169,9 +120,9 @@ class UserCompany < ActiveRecord::Base
169
120
 
170
121
  class Company < ActiveRecord::Base
171
122
  has_many :users, :through => :user_company
172
- acts_has_many :through => true
173
-
174
123
  has_many :user_company
124
+
125
+ acts_has_many :users, :through => true
175
126
  end
176
127
 
177
128
  class User < ActiveRecord::Base
@@ -186,6 +137,35 @@ user.companies = new_rows # update user companies
186
137
  Company.delete(delete_ids) # for delete_ids from has_many_update_through best way is to use "delete" and miss unnecessary check
187
138
  ```
188
139
 
140
+ ### Use acts_has_many_for with acts_has_many
141
+ When use acts_has_many_for you can user attributes
142
+ ```ruby
143
+
144
+ user = User.create name: 'Bill', company_attributes: { title: 'Microsoft' }
145
+
146
+ or
147
+
148
+ user.company_attributes = { title: 'Google' }
149
+
150
+ or
151
+
152
+ user.company_attributes = Company.first
153
+ user.save
154
+
155
+ # if you use has_many_through you can use collection
156
+
157
+ user = User.create name: 'Bob', companies_collection: [{title: 'MS'}, {title: 'Google'}]
158
+
159
+ or
160
+
161
+ user.companies_collection = [{title: 'MS'}, {title: 'Google'}]
162
+
163
+ or
164
+
165
+ user.companies_collection = Company.all
166
+ user.save
167
+
168
+ ```
189
169
 
190
170
  Contributing
191
171
  ------------
@@ -193,7 +173,6 @@ You can help improve this project.
193
173
 
194
174
  Here are some ways *you* can contribute:
195
175
 
196
- * by using alpha, beta, and prerelease versions
197
176
  * by reporting bugs
198
177
  * by suggesting new features
199
178
  * by writing or editing documentation
@@ -0,0 +1,143 @@
1
+ module ActiveRecord
2
+ module ActsHasMany
3
+
4
+ # Added:
5
+ # class methods:
6
+ # +has_many_through_update+
7
+ #
8
+ # instance methods:
9
+ # +has_many_update+
10
+ # +update_with_<relation>+
11
+ # +actuale?+
12
+
13
+ module Child
14
+
15
+ def self.included base
16
+
17
+ # Generate methods:
18
+ # update_with_<relation>(data)
19
+
20
+ base.dependent_relations.each do |relation|
21
+ define_method("update_with_#{relation}") do |data|
22
+ has_many_update data, relation
23
+ end
24
+ end
25
+ end
26
+
27
+ #
28
+ # +has_many_update+ (return: array) - [new_record, delete_record]
29
+ # options: (Hash is deprecated, list parameters)
30
+ # data (type: hash) - data for updte
31
+ # relation (type: str, symbol) - current relation for check
32
+ #
33
+
34
+ def has_many_update data, relation
35
+ data = { model.compare => ''}.merge data
36
+
37
+ if relation.blank?
38
+ warn "[ARRGUMENT MISSING]: 'has_many_update' don't know about current relation, and check all relations"
39
+ end
40
+
41
+ has_many_cleaner data.symbolize_keys, relation
42
+ end
43
+
44
+ #
45
+ # +actuale?+ - check the acutuality of element in has_many table
46
+ # options:
47
+ # relation (String, Symbol) - for exclude one record from current relation
48
+ #
49
+
50
+ def actuale? opt = ""
51
+ relation = opt.to_s.tableize
52
+
53
+ actuale = false
54
+ model.dependent_relations.each do |dependent_relation|
55
+ tmp = self.send dependent_relation
56
+ if relation == dependent_relation
57
+ actuale ||= tmp.all.size > 1
58
+ else
59
+ actuale ||= tmp.exists?
60
+ end
61
+ end
62
+ actuale
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ #
69
+ # +destroy_filter+ - use with before_filter, and protect actuale records
70
+ #
71
+
72
+ def destroy_filter
73
+ not actuale?
74
+ end
75
+
76
+ #
77
+ # +has_many_cleaner+ - base mothod
78
+ #
79
+
80
+ def has_many_cleaner data, relation
81
+ compare = { model.compare => data[model.compare] }
82
+
83
+ new_record = self
84
+ del_record = nil
85
+
86
+ if actuale? relation
87
+ new_record = model.where(compare).first_or_create data
88
+ else
89
+ object_tmp = model.where(compare).first
90
+ unless object_tmp.nil?
91
+ del_record = (new_record.id == object_tmp.id) ? nil : new_record
92
+ new_record = object_tmp
93
+ else
94
+ if new_record.id.nil?
95
+ new_record = model.where(compare).first_or_create data
96
+ else
97
+ if data[model.compare].blank?
98
+ del_record, new_record = new_record, nil
99
+ else
100
+ update_attributes data
101
+ end
102
+ end
103
+ end
104
+ end
105
+ [new_record, del_record]
106
+ end
107
+
108
+
109
+ module ChildThrough
110
+
111
+ #
112
+ # +has_many_through_update+ (return array) [ 1 - array new records, 2 - array delete records ]
113
+ # options
114
+ # :update (array) - data for update (id and data)
115
+ # :new (array) - data for create record (data)
116
+ #
117
+ # +for delete records need use method destroy !!!+
118
+ #
119
+
120
+ def has_many_through_update(options)
121
+ record_add = []
122
+ record_del = []
123
+
124
+ options[:update].each do |id, data|
125
+ add, del = find(id).has_many_update data, options[:relation]
126
+ record_add << add unless add.nil?
127
+ record_del << del unless del.nil?
128
+ end unless options[:update].nil?
129
+
130
+ unless options[:new].nil?
131
+ options[:new].uniq!
132
+ options[:new].each do |data|
133
+ data = data.symbolize_keys
134
+ record_add << where(compare => data[compare]).first_or_create(data)
135
+ record_del.delete record_add.last
136
+ end
137
+ end
138
+
139
+ [record_add, record_del]
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,106 @@
1
+ module ActiveRecord
2
+ module ActsHasMany
3
+
4
+ #
5
+ # Add class methods:
6
+ # +<relation>_attributes=+
7
+ # or
8
+ # +<relation>_collection=+
9
+ #
10
+
11
+ module Parent
12
+
13
+ def self.included base
14
+
15
+ #
16
+ # Generate class methods
17
+ # +<relation>_attributes=+ - set one element (use with belogns_to)
18
+ # options:
19
+ # data (type: Hash, existing recod, NilClass)
20
+ #
21
+ # Create or update child record and set to parent when `data` is Hash
22
+ # Change data with delating or leaving child record when `data` is exists record, or NilClass
23
+ #
24
+ # =>------------------------------------------------------------------
25
+ #
26
+ # +<relation>_collection=+ - set many elements (use with has_many :through)
27
+ # options:
28
+ # data (type: Array with (exist records or Hash)
29
+ #
30
+ # Create or select exists records and set to parent, unused record try destory
31
+ #
32
+
33
+ base.dependent_relations.each do |relation|
34
+ unless base.reflect_on_association relation.to_sym
35
+ raise ArgumentError, "No association found for name `#{relation}`. Has it been defined yet?"
36
+ end
37
+
38
+ relation = relation.to_s
39
+ unless base.reflect_on_association(relation.to_sym).collection?
40
+ base.class_eval <<-EOV, __FILE__ , __LINE__
41
+ def #{relation}_attributes= data
42
+ self.tmp_garbage ||= {}
43
+ current = self.#{relation}
44
+
45
+ if data.is_a? Hash
46
+ if current
47
+ new, del = current.has_many_update data, '#{base.name.tableize}'
48
+ else
49
+ new, del = #{relation.classify}.new.has_many_update data, '#{base.name.tableize}'
50
+ end
51
+
52
+ self.#{relation} = new
53
+ self.tmp_garbage.merge!({ #{relation}: del }) if del
54
+ else
55
+ self.#{relation} = data
56
+ self.tmp_garbage.merge!({ #{relation}: current }) if current
57
+ end
58
+ end
59
+ EOV
60
+ else
61
+ base.class_eval <<-EOV, __FILE__ , __LINE__
62
+ def #{relation}_collection= data
63
+ self.tmp_garbage ||= {}
64
+
65
+ if data.is_a? Array
66
+ if data.first.is_a? Hash
67
+ new, del = #{relation.classify}.has_many_through_update new: data, relation: '#{base.name.tableize}'
68
+ elsif data.first.is_a? #{relation.classify}
69
+ new, del = data, []
70
+ elsif data.empty?
71
+ new, del = [],[]
72
+ else
73
+ raise ArgumentError, "Array with (Hash or " + #{relation.classify}.inspect + ") expected, got Array with " + data.first.inspect
74
+ end
75
+
76
+ #{relation}.each{ |t| del << t if not new.include? t }
77
+ self.#{relation} = new
78
+ self.tmp_garbage.merge!({ #{relation}: del }) if del.present?
79
+ else
80
+ raise ArgumentError, "Array expected, got " + data.inspect
81
+ end
82
+ end
83
+ EOV
84
+ end
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ #
91
+ # +clear_garbage+ work with after_save and try destroied records from tmp_garbage
92
+ #
93
+
94
+ def clear_garbage
95
+ self.tmp_garbage.each do |relation, record|
96
+ if record.is_a? Array
97
+ record.each { |r| r.destroy }
98
+ else
99
+ record.destroy
100
+ end
101
+ end if self.tmp_garbage.present?
102
+ self.tmp_garbage = {}
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,100 @@
1
+ module ActiveRecord
2
+ module ActsHasMany
3
+
4
+ #
5
+ # Add class methods: (for use in your model)
6
+ # +acts_has_many_for+
7
+ # +acts_has_many+
8
+ #
9
+
10
+ def self.included base
11
+ base.extend ClassMethods
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ # Add class methods:
17
+ # +dependent_relations+
18
+ # +compare+
19
+ # +model+
20
+ # +has_many_through_update+
21
+ #
22
+ # Add instance mothods:
23
+ # +actuale?+
24
+ # +has_many_update+
25
+ # +update_with_<relation>+
26
+ #
27
+ # Set:
28
+ # validates for <compare_element> (uniqueness: true, presence: true)
29
+ # before destroy filter
30
+ #
31
+
32
+ def acts_has_many *opt
33
+ options = { compare: :title, through: false }
34
+ options.update opt.extract_options!
35
+ options.assert_valid_keys :compare, :through
36
+ options[:relations] = opt
37
+
38
+ options[:relations] = self.reflect_on_all_associations(:has_many)
39
+ .map(&:name) if options[:relations].blank?
40
+
41
+ dependent_relations = []
42
+ options[:relations].each do |relation|
43
+ if reflect_on_association relation.to_sym
44
+ dependent_relations << relation.to_s.tableize
45
+ else
46
+ raise ArgumentError, "No association found for name `#{relation}'. Has it been defined yet?"
47
+ end
48
+ end
49
+
50
+ class_eval <<-EOV, __FILE__ , __LINE__
51
+ def self.dependent_relations
52
+ #{dependent_relations}
53
+ end
54
+
55
+ def self.compare
56
+ '#{options[:compare]}'.to_sym
57
+ end
58
+
59
+ include ActiveRecord::ActsHasMany::Child
60
+ #{'extend ActiveRecord::ActsHasMany::ChildThrough' if options[:through]}
61
+
62
+ def model
63
+ #{self}
64
+ end
65
+
66
+ validates :#{options[:compare]}, uniqueness: true, presence: true
67
+ before_destroy :destroy_filter
68
+ EOV
69
+ end
70
+
71
+ #
72
+ # Add class methods:
73
+ # +dependent_relations+
74
+ #
75
+ # +<relation>_attributes=+
76
+ # or
77
+ # +<relation>_collection=+
78
+ #
79
+ # Set:
80
+ # after save filter
81
+ # attribut accessor tmp_garbage
82
+ #
83
+
84
+ def acts_has_many_for *relations
85
+ class_eval <<-EOV, __FILE__ , __LINE__
86
+ def self.dependent_relations
87
+ #{relations}
88
+ end
89
+
90
+ attr_accessor :tmp_garbage
91
+
92
+ include ActiveRecord::ActsHasMany::Parent
93
+
94
+ after_save :clear_garbage
95
+ EOV
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -1,3 +1,3 @@
1
1
  module ActsHasMany
2
- VERSION = "0.1.5"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/acts_has_many.rb CHANGED
@@ -1,2 +1,5 @@
1
- require 'acts_has_many/active_record/acts/has_many'
2
- ActiveRecord::Base.class_eval { include ActiveRecord::Acts::HasMany}
1
+ require 'acts_has_many/active_record/acts_has_many'
2
+ require 'acts_has_many/active_record/acts_has_many/child'
3
+ require 'acts_has_many/active_record/acts_has_many/parent'
4
+
5
+ ActiveRecord::Base.class_eval { include ActiveRecord::ActsHasMany}