acts_has_many 0.1.5 → 0.2.0

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/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}