OSDN Git Service

Regular updates
[twpd/master.git] / rails-models.md
1 ---
2 title: Rails models
3 category: Rails
4 layout: 2017/sheet
5 ---
6
7 Generating
8 ----------
9
10 ### Generating
11
12     $ rails g model User
13
14 Using models
15 ------------
16
17 ### Query methods
18
19 ```ruby
20 items = Model
21   .where(first_name: 'Harvey')
22   .where('id = 3')
23   .where('id = ?', 3)
24 ```
25
26 ```ruby
27   .order(:title)
28   .order(title: :desc)
29   .order("title DESC")
30 ```
31
32 ```ruby
33   .reorder(:title)  # discards other .order's
34   .rewhere(...)     # discards other .where's
35 ```
36
37 ```ruby
38   .limit(2)
39   .offset(1)
40   .uniq
41 ```
42
43 See: [QueryMethods](http://devdocs.io/rails/activerecord/querymethods)
44
45 ### Advanced query methods
46
47 ```ruby
48 items = Model
49   .select(:id)
50   .select([:id, :name])
51 ```
52
53 ```ruby
54   .group(:name)   # GROUP BY name
55   .group('name AS grouped_name, age')
56   .having('SUM(price) > 30')  # needs to be chained with .group
57 ```
58
59 ```ruby
60   .includes(:user)
61   .includes(user: [:articles])
62 ```
63
64 ```ruby
65   .references(:posts)
66   # aka: .where("posts.name = 'foo'").references(:posts)
67 ```
68
69 ### Finder methods
70
71 ```ruby
72 item = Model.find(id)
73 item = Model.find_by_email(email)
74 item = Model.where(email: email).first
75 ```
76
77 ```ruby
78 Model
79   .exists?(5)
80   .exists?(name: "David")
81 ```
82
83 ```ruby
84   .first
85   .last
86   .find_nth(4, [offset])
87 ```
88
89 See: [FinderMethods](http://devdocs.io/rails/activerecord/findermethods)
90
91 ### Persistence
92
93 ```ruby
94 item.new_record?
95 item.persisted?
96 item.destroyed?
97
98 item.serialize_hash
99 ```
100
101 ```ruby
102 item.save
103 item.save!      # Same as above, but raises an Exception
104 ```
105
106 ```ruby
107 item.update  name: 'John'  # Saves immediately
108 item.update! name: 'John'
109 ```
110
111 ```ruby
112 item.update_column  :name, 'John'  # skips validations and callbacks
113 item.update_columns  name: 'John'
114 item.update_columns! name: 'John'
115 ```
116
117 ```ruby
118 item.touch                 # updates :updated_at
119 item.touch :published_at
120 ```
121
122 ```ruby
123 item.destroy
124 item.delete  # skips callbacks
125 ```
126
127 ```ruby
128 Model.create     # Same an #new then #save
129 Model.create!    # Same as above, but raises an Exception
130 ```
131
132 See: [Persistence](http://devdocs.io/rails/activerecord/persistence)
133
134 ### Attribute Assignment
135
136 ```ruby
137 item.attributes                      # #<Hash>
138 ```
139
140 ```ruby
141 item.attributes = { name: 'John' }   # Merges attributes in. Doesn't save.
142 item.assign_attributes name: 'John'  # Same as above
143 ```
144
145 See: [AttributeAssignment](http://devdocs.io/rails/activerecord/attributeassignment)
146
147 ### Dirty
148
149 ```ruby
150 item.changed?
151 item.changed             # ['name']
152 item.changed_attributes  # { 'name' => 'Bob' } - original values
153 item.changes             # { 'name' => ['Bob', 'Robert'] }
154 item.previous_changes    # available after #save
155 item.restore_attributes
156 ```
157
158 ```ruby
159 item.name = 'Robert'
160 item.name_was         # 'Bob'
161 item.name_change      # [ 'Bob', 'Robert' ]
162 item.name_changed?    # true
163 item.name_changed?(from: 'Bob', to: 'Robert')
164 ```
165
166 See: [Dirty](http://devdocs.io/rails/activemodel/dirty)
167
168 ### Validations
169
170 ```ruby
171 item.valid?
172 item.invalid?
173 ```
174
175 See: [Validations](http://devdocs.io/rails/activerecord/validations)
176
177 ### Calculations
178
179 ```ruby
180 Person.count
181 Person.count(:age)    # counts non-nil's
182 ```
183
184 ```ruby
185 Person.average(:age)
186 Person.maximum(:age)
187 Person.minimum(:age)
188 Person.sum('2 * age')
189 ```
190
191 ```ruby
192 Person.calculate(:count, :all)
193 ```
194
195 Advanced:
196
197 ```ruby
198 Person.distinct.count
199 Person.group(:city).count
200 ```
201
202 See: [Calculations](http://devdocs.io/rails/activerecord/calculations)
203
204 ### Dynamic attribute-based finders
205
206 Given a field called `name`:
207 {: .-setup}
208
209 ```ruby
210 # Returns one record
211 Person.find_by_name(name)
212 Person.find_last_by_name(name)
213 Person.find_or_create_by_name(name)
214 Person.find_or_initialize_by_name(name)
215 ```
216
217 ```ruby
218 # Returns a list of records
219 Person.find_all_by_name(name)
220 ```
221
222 ```ruby
223 # Add a bang to make it raise an exception
224 Person.find_by_name!(name)
225 ```
226
227 ```ruby
228 # You may use `scoped` instead of `find`
229 Person.scoped_by_user_name
230 ```
231
232 Associations
233 ------------
234
235 ### Associations
236
237 - `belongs_to`
238 - `has_one`
239 - `has_many`
240 - `has_many :through`
241 - `has_one :through`
242 - `has_and_belongs_to_many`
243
244 ### Has many
245
246 ```ruby
247 belongs_to :parent, :foreign_key => 'parent_id' class_name: 'Folder'
248 has_many :folders, :foreign_key => 'parent_id', class_name: 'Folder'
249
250 has_many :comments,                -> { order('posted_on DESC') }
251 has_many :comments,    :include    => :author
252 has_many :people,      :class_name => "Person"
253 has_many :people,      :conditions => "deleted = 0"
254 has_many :tracks,                  -> { order(:position) }
255 has_many :comments,    :dependent  => :nullify
256 has_many :comments,    :dependent  => :destroy
257 has_many :tags,        :as         => :taggable
258 has_many :reports,     :readonly   => true
259 has_many :subscribers, :through    => :subscriptions, class_name: "User", :source => :user
260 has_many :subscribers, :finder_sql =>
261     'SELECT DISTINCT people.* ' +
262     'FROM people p, post_subscriptions ps ' +
263     'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
264     'ORDER BY p.first_name'
265 ```
266
267 ### belongs to
268
269 ```ruby
270 belongs_to :author,
271   :dependent      => :destroy    # or :delete
272
273   :class_name     => "Person"
274   :select         => "*"
275   :counter_cache  => true
276   :counter_cache  => :custom_counter
277   :include        => "Book"
278   :readonly       => true
279
280   :conditions     => 'published = true'
281
282   :touch          => true
283   :touch          => :authors_last_updated_at
284
285   :primary_key    => "name"
286   :foreign_key    => "author_name"
287 ```
288
289 ### Many-to-many
290
291 If you have a join model:
292 {: .-setup}
293
294 ```ruby
295 class Programmer < ActiveRecord::Base
296   has_many :assignments
297   has_many :projects, :through => :assignments
298 end
299 ```
300 {: data-line="2,3"}
301
302 ```ruby
303 class Project < ActiveRecord::Base
304   has_many :assignments
305   has_many :programmers, :through => :assignments
306 end
307 ```
308 {: data-line="2,3"}
309
310 ```ruby
311 class Assignment
312   belongs_to :project
313   belongs_to :programmer
314 end
315 ```
316 {: data-line="2,3"}
317
318 ### Many-to-many (HABTM)
319
320 ```ruby
321 has_and_belongs_to_many :projects
322 has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
323 has_and_belongs_to_many :nations, :class_name => "Country"
324 has_and_belongs_to_many :categories, :join_table => "prods_cats"
325 has_and_belongs_to_many :categories, :readonly => true
326 has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
327 "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}"
328 ```
329
330 ### Polymorphic associations
331
332 ```ruby
333 class Post
334   has_many :attachments, as: :parent
335 end
336 ```
337 {: data-line="2"}
338
339 ```ruby
340 class Image
341   belongs_to :parent, polymorphic: true
342 end
343 ```
344 {: data-line="2"}
345
346 And in migrations:
347
348 ```ruby
349 create_table :images do |t|
350   t.references :post, polymorphic: true
351 end
352 ```
353 {: data-line="2"}
354
355 Validation
356 ----------
357
358 ### Validation
359
360 ```ruby
361 class Person < ActiveRecord::Base
362 ```
363 {:.-setup}
364
365 ```ruby
366   # Presence
367   validates :name,     presence: true
368 ```
369 {: data-line="2"}
370
371 ```ruby
372   # Acceptance
373   validates :terms,    acceptance: true
374 ```
375
376 ```ruby
377   # Confirm
378   validates :email,    confirmation: true
379 ```
380
381 ```ruby
382   # Unique
383   validates :slug,     uniqueness: true
384   validates :slug,     uniqueness: { case_sensitive: false }
385   validates :holiday,  uniqueness: { scope: :year, message: 'yearly only' }
386 ```
387
388 ```ruby
389   # Format
390   validates :code,     format: /regex/
391   validates :code,     format: { with: /regex/ }
392 ```
393
394 ```ruby
395   # Length
396   validates :name,     length: { minimum: 2 }
397   validates :bio,      length: { maximum: 500 }
398   validates :password, length: { in: => 6..20 }
399   validates :number,   length: { is: => 6 }
400 ```
401
402 ```ruby
403   # Include/exclude
404   validates :gender,   inclusion: %w(male female)
405   validates :gender,   inclusion: { in: %w(male female) }
406   validates :lol,      exclusion: %w(xyz)
407 ```
408
409 ```ruby
410   # Numeric
411   validates :points,   numericality: true
412   validates :played,   numericality: { only_integer: true }
413   # ... greater_than, greater_than_or_equal_to,
414   # ... less_than, less_than_or_equal_to
415   # ... odd, even, equal_to
416 ```
417
418 ```ruby
419   # Validate the associated records to ensure they're valid as well
420   has_many :books
421   validates_associated :books
422 ```
423
424 ```ruby
425   # Length (full options)
426   validates :content, length: {
427     minimum:   300,
428     maximum:   400,
429     tokenizer: lambda { |str| str.scan(/\w+/) },
430     too_short: "must have at least %{count} words",
431     too_long:  "must have at most %{count} words" }
432 ```
433
434 ```ruby
435   # Multiple
436   validates :login, :email, presence: true
437 ```
438
439 ```ruby
440   # Conditional
441   validates :description, presence: true, if: :published?
442   validates :description, presence: true, if: lambda { |obj| .. }
443 ```
444
445 ```ruby
446   validates :title, presence: true, on: :save   # :save | :create | :update
447 ```
448
449 ```ruby
450 end
451 ```
452 {: .-setup}
453
454 ### Custom validations
455
456 ```ruby
457 class Person < ActiveRecord::Base
458   validate :foo_cant_be_nil
459
460   def foo_cant_be_nil
461     errors.add(:foo, 'cant be nil')  if foo.nil?
462   end
463 end
464 ```
465 {: data-line="2"}
466
467 ### Errors
468
469 ```ruby
470 record.errors.valid?      # → false
471 record.errors             # → { :name => ["can't be blank"] }
472 record.errors.messages    # → { :name => ["can't be blank"] }
473 ```
474
475 ```ruby
476 record.errors[:name].any?
477 ```
478
479 Other API
480 ---------
481
482 ### Callbacks
483
484  * [Guides: callbacks](http://guides.rubyonrails.org/active_record_validations_callbacks.html)
485
486 ### Mass updates
487
488  ```ruby
489 # Updates person id 15
490 Person.update 15, name: "John", age: 24
491 Person.update [1,2], [{name: "John"}, {name: "foo"}]
492 ```
493
494 ### Joining
495
496 ```ruby
497 # Basic joins
498 Student.joins(:schools).where(schools: { type: 'public' })
499 Student.joins(:schools).where('schools.type' => 'public' )
500 ```
501
502 ```ruby
503 # Multiple associations
504 Article.joins(:category, :comments)
505 ```
506
507 ```ruby
508 # Nested associations
509 Article.joins(comments: :guest)
510 ```
511
512 ```ruby
513 # SQL
514 Author.joins(
515   'INNER JOIN posts ' +
516   'ON posts.author_id = authors.id ' +
517   'AND posts.published = "t"'
518 )
519 ```
520
521 ### Where interpolation
522
523 ```ruby
524 where('name = ?', 'John')
525 where(['name = :name', { name: 'John' }])
526 ```
527
528 ### Serialize
529
530 ```ruby
531 class User < ActiveRecord::Base
532   serialize :preferences
533 end
534 ```
535 {: data-line="2"}
536
537 ```ruby
538 user = User.create(
539   preferences: {
540     'background' => 'black',
541     'display' => 'large'
542   }
543 )
544 ```
545
546 You can also specify a class option as the second parameter that’ll raise an 
547 exception if a serialized object is retrieved as a descendant of a class not in 
548 the hierarchy.
549
550 ```ruby
551 # Only Hash allowed!
552 class User < ActiveRecord::Base
553   serialize :preferences, Hash
554 end
555 ```
556 {: data-line="3"}
557
558 ```ruby
559 # Reading it raises SerializationTypeMismatch
560 user = User.create(preferences: %w(one two three))
561 User.find(user.id).preferences
562 ```
563
564 Other tricks
565 ------------
566
567 ### Overriding accessors
568
569 ```ruby
570 class Song < ActiveRecord::Base
571   # Uses an integer of seconds to hold the length of the song
572
573   def length=(minutes)
574     write_attribute(:length, minutes.to_i * 60)
575   end
576
577   def length
578     read_attribute(:length) / 60
579   end
580 end
581 ```
582 {: data-line="4,8"}
583
584 See: <http://api.rubyonrails.org/classes/ActiveRecord/Base.html>
585
586 Callbacks
587 ---------
588
589 - after_initialize
590 - before_validation / after_validation
591 - before_save / after_save / around_save
592 - before_create / after_create / around_create
593 - before_update / after_update / around_update
594 - before_destroy / after_destroy / around_destroy
595 - after_commit
596 - after_rollback
597
598 See: [ActiveRecord Callbacks](https://guides.rubyonrails.org/active_record_callbacks.html)