PageRenderTime 131ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/guides/source/active_model_basics.md

https://gitlab.com/mpivaa/rails
Markdown | 556 lines | 414 code | 142 blank | 0 comment | 0 complexity | c930ed3e09a168c8fffaa46290572abf MD5 | raw file
  1. **DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
  2. Active Model Basics
  3. ===================
  4. This guide should provide you with all you need to get started using model
  5. classes. Active Model allows for Action Pack helpers to interact with
  6. plain Ruby objects. Active Model also helps build custom ORMs for use
  7. outside of the Rails framework.
  8. After reading this guide, you will be able to add to plain Ruby objects:
  9. * The ability to behave like an Active Record model.
  10. * Callbacks and validations like Active Record.
  11. * Serializers.
  12. * Integration with the Rails internationalization (i18n) framework.
  13. --------------------------------------------------------------------------------
  14. Introduction
  15. ------------
  16. Active Model is a library containing various modules used in developing
  17. classes that need some features present on Active Record.
  18. Some of these modules are explained below.
  19. ### Attribute Methods
  20. The `ActiveModel::AttributeMethods` module can add custom prefixes and suffixes
  21. on methods of a class. It is used by defining the prefixes and suffixes and
  22. which methods on the object will use them.
  23. ```ruby
  24. class Person
  25. include ActiveModel::AttributeMethods
  26. attribute_method_prefix 'reset_'
  27. attribute_method_suffix '_highest?'
  28. define_attribute_methods 'age'
  29. attr_accessor :age
  30. private
  31. def reset_attribute(attribute)
  32. send("#{attribute}=", 0)
  33. end
  34. def attribute_highest?(attribute)
  35. send(attribute) > 100
  36. end
  37. end
  38. person = Person.new
  39. person.age = 110
  40. person.age_highest? # => true
  41. person.reset_age # => 0
  42. person.age_highest? # => false
  43. ```
  44. ### Callbacks
  45. `ActiveModel::Callbacks` gives Active Record style callbacks. This provides an
  46. ability to define callbacks which run at appropriate times.
  47. After defining callbacks, you can wrap them with before, after and around
  48. custom methods.
  49. ```ruby
  50. class Person
  51. extend ActiveModel::Callbacks
  52. define_model_callbacks :update
  53. before_update :reset_me
  54. def update
  55. run_callbacks(:update) do
  56. # This method is called when update is called on an object.
  57. end
  58. end
  59. def reset_me
  60. # This method is called when update is called on an object as a before_update callback is defined.
  61. end
  62. end
  63. ```
  64. ### Conversion
  65. If a class defines `persisted?` and `id` methods, then you can include the
  66. `ActiveModel::Conversion` module in that class and call the Rails conversion
  67. methods on objects of that class.
  68. ```ruby
  69. class Person
  70. include ActiveModel::Conversion
  71. def persisted?
  72. false
  73. end
  74. def id
  75. nil
  76. end
  77. end
  78. person = Person.new
  79. person.to_model == person # => true
  80. person.to_key # => nil
  81. person.to_param # => nil
  82. ```
  83. ### Dirty
  84. An object becomes dirty when it has gone through one or more changes to its
  85. attributes and has not been saved. `ActiveModel::Dirty` gives the ability to
  86. check whether an object has been changed or not. It also has attribute based
  87. accessor methods. Let's consider a Person class with attributes `first_name`
  88. and `last_name`:
  89. ```ruby
  90. class Person
  91. include ActiveModel::Dirty
  92. define_attribute_methods :first_name, :last_name
  93. def first_name
  94. @first_name
  95. end
  96. def first_name=(value)
  97. first_name_will_change!
  98. @first_name = value
  99. end
  100. def last_name
  101. @last_name
  102. end
  103. def last_name=(value)
  104. last_name_will_change!
  105. @last_name = value
  106. end
  107. def save
  108. # do save work...
  109. changes_applied
  110. end
  111. end
  112. ```
  113. #### Querying object directly for its list of all changed attributes.
  114. ```ruby
  115. person = Person.new
  116. person.changed? # => false
  117. person.first_name = "First Name"
  118. person.first_name # => "First Name"
  119. # returns if any attribute has changed.
  120. person.changed? # => true
  121. # returns a list of attributes that have changed before saving.
  122. person.changed # => ["first_name"]
  123. # returns a hash of the attributes that have changed with their original values.
  124. person.changed_attributes # => {"first_name"=>nil}
  125. # returns a hash of changes, with the attribute names as the keys, and the values will be an array of the old and new value for that field.
  126. person.changes # => {"first_name"=>[nil, "First Name"]}
  127. ```
  128. #### Attribute based accessor methods
  129. Track whether the particular attribute has been changed or not.
  130. ```ruby
  131. # attr_name_changed?
  132. person.first_name # => "First Name"
  133. person.first_name_changed? # => true
  134. ```
  135. Track what was the previous value of the attribute.
  136. ```ruby
  137. # attr_name_was accessor
  138. person.first_name_was # => nil
  139. ```
  140. Track both previous and current value of the changed attribute. Returns an array
  141. if changed, else returns nil.
  142. ```ruby
  143. # attr_name_change
  144. person.first_name_change # => [nil, "First Name"]
  145. person.last_name_change # => nil
  146. ```
  147. ### Validations
  148. `ActiveModel::Validations` module adds the ability to validate class objects
  149. like in Active Record.
  150. ```ruby
  151. class Person
  152. include ActiveModel::Validations
  153. attr_accessor :name, :email, :token
  154. validates :name, presence: true
  155. validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
  156. validates! :token, presence: true
  157. end
  158. person = Person.new
  159. person.token = "2b1f325"
  160. person.valid? # => false
  161. person.name = 'vishnu'
  162. person.email = 'me'
  163. person.valid? # => false
  164. person.email = 'me@vishnuatrai.com'
  165. person.valid? # => true
  166. person.token = nil
  167. person.valid? # => raises ActiveModel::StrictValidationFailed
  168. ```
  169. ### Naming
  170. `ActiveModel::Naming` adds a number of class methods which make the naming and routing
  171. easier to manage. The module defines the `model_name` class method which
  172. will define a number of accessors using some `ActiveSupport::Inflector` methods.
  173. ```ruby
  174. class Person
  175. extend ActiveModel::Naming
  176. end
  177. Person.model_name.name # => "Person"
  178. Person.model_name.singular # => "person"
  179. Person.model_name.plural # => "people"
  180. Person.model_name.element # => "person"
  181. Person.model_name.human # => "Person"
  182. Person.model_name.collection # => "people"
  183. Person.model_name.param_key # => "person"
  184. Person.model_name.i18n_key # => :person
  185. Person.model_name.route_key # => "people"
  186. Person.model_name.singular_route_key # => "person"
  187. ```
  188. ### Model
  189. `ActiveModel::Model` adds the ability to a class to work with Action Pack and
  190. Action View right out of the box.
  191. ```ruby
  192. class EmailContact
  193. include ActiveModel::Model
  194. attr_accessor :name, :email, :message
  195. validates :name, :email, :message, presence: true
  196. def deliver
  197. if valid?
  198. # deliver email
  199. end
  200. end
  201. end
  202. ```
  203. When including `ActiveModel::Model` you get some features like:
  204. - model name introspection
  205. - conversions
  206. - translations
  207. - validations
  208. It also gives you the ability to initialize an object with a hash of attributes,
  209. much like any Active Record object.
  210. ```ruby
  211. email_contact = EmailContact.new(name: 'David',
  212. email: 'david@example.com',
  213. message: 'Hello World')
  214. email_contact.name # => 'David'
  215. email_contact.email # => 'david@example.com'
  216. email_contact.valid? # => true
  217. email_contact.persisted? # => false
  218. ```
  219. Any class that includes `ActiveModel::Model` can be used with `form_for`,
  220. `render` and any other Action View helper methods, just like Active Record
  221. objects.
  222. ### Serialization
  223. `ActiveModel::Serialization` provides a basic serialization for your object.
  224. You need to declare an attributes hash which contains the attributes you want to
  225. serialize. Attributes must be strings, not symbols.
  226. ```ruby
  227. class Person
  228. include ActiveModel::Serialization
  229. attr_accessor :name
  230. def attributes
  231. {'name' => nil}
  232. end
  233. end
  234. ```
  235. Now you can access a serialized hash of your object using the `serializable_hash`.
  236. ```ruby
  237. person = Person.new
  238. person.serializable_hash # => {"name"=>nil}
  239. person.name = "Bob"
  240. person.serializable_hash # => {"name"=>"Bob"}
  241. ```
  242. #### ActiveModel::Serializers
  243. Rails provides two serializers `ActiveModel::Serializers::JSON` and
  244. `ActiveModel::Serializers::Xml`. Both of these modules automatically include
  245. the `ActiveModel::Serialization`.
  246. ##### ActiveModel::Serializers::JSON
  247. To use the `ActiveModel::Serializers::JSON` you only need to change from
  248. `ActiveModel::Serialization` to `ActiveModel::Serializers::JSON`.
  249. ```ruby
  250. class Person
  251. include ActiveModel::Serializers::JSON
  252. attr_accessor :name
  253. def attributes
  254. {'name' => nil}
  255. end
  256. end
  257. ```
  258. With the `as_json` you have a hash representing the model.
  259. ```ruby
  260. person = Person.new
  261. person.as_json # => {"name"=>nil}
  262. person.name = "Bob"
  263. person.as_json # => {"name"=>"Bob"}
  264. ```
  265. From a JSON string you define the attributes of the model.
  266. You need to have the `attributes=` method defined on your class:
  267. ```ruby
  268. class Person
  269. include ActiveModel::Serializers::JSON
  270. attr_accessor :name
  271. def attributes=(hash)
  272. hash.each do |key, value|
  273. send("#{key}=", value)
  274. end
  275. end
  276. def attributes
  277. {'name' => nil}
  278. end
  279. end
  280. ```
  281. Now it is possible to create an instance of person and set the attributes using `from_json`.
  282. ```ruby
  283. json = { name: 'Bob' }.to_json
  284. person = Person.new
  285. person.from_json(json) # => #<Person:0x00000100c773f0 @name="Bob">
  286. person.name # => "Bob"
  287. ```
  288. ##### ActiveModel::Serializers::Xml
  289. To use the `ActiveModel::Serializers::Xml` you only need to change from
  290. `ActiveModel::Serialization` to `ActiveModel::Serializers::Xml`.
  291. ```ruby
  292. class Person
  293. include ActiveModel::Serializers::Xml
  294. attr_accessor :name
  295. def attributes
  296. {'name' => nil}
  297. end
  298. end
  299. ```
  300. With the `to_xml` you have an XML representing the model.
  301. ```ruby
  302. person = Person.new
  303. person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name nil=\"true\"/>\n</person>\n"
  304. person.name = "Bob"
  305. person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name>Bob</name>\n</person>\n"
  306. ```
  307. From an XML string you define the attributes of the model.
  308. You need to have the `attributes=` method defined on your class:
  309. ```ruby
  310. class Person
  311. include ActiveModel::Serializers::Xml
  312. attr_accessor :name
  313. def attributes=(hash)
  314. hash.each do |key, value|
  315. send("#{key}=", value)
  316. end
  317. end
  318. def attributes
  319. {'name' => nil}
  320. end
  321. end
  322. ```
  323. Now it is possible to create an instance of person and set the attributes using `from_xml`.
  324. ```ruby
  325. xml = { name: 'Bob' }.to_xml
  326. person = Person.new
  327. person.from_xml(xml) # => #<Person:0x00000100c773f0 @name="Bob">
  328. person.name # => "Bob"
  329. ```
  330. ### Translation
  331. `ActiveModel::Translation` provides integration between your object and the Rails
  332. internationalization (i18n) framework.
  333. ```ruby
  334. class Person
  335. extend ActiveModel::Translation
  336. end
  337. ```
  338. With the `human_attribute_name` you can transform attribute names into a more
  339. human format. The human format is defined in your locale file.
  340. * config/locales/app.pt-BR.yml
  341. ```yml
  342. pt-BR:
  343. activemodel:
  344. attributes:
  345. person:
  346. name: 'Nome'
  347. ```
  348. ```ruby
  349. Person.human_attribute_name('name') # => "Nome"
  350. ```
  351. ### Lint Tests
  352. `ActiveModel::Lint::Tests` allow you to test whether an object is compliant with
  353. the Active Model API.
  354. * app/models/person.rb
  355. ```ruby
  356. class Person
  357. include ActiveModel::Model
  358. end
  359. ```
  360. * test/models/person_test.rb
  361. ```ruby
  362. require 'test_helper'
  363. class PersonTest < ActiveSupport::TestCase
  364. include ActiveModel::Lint::Tests
  365. def setup
  366. @model = Person.new
  367. end
  368. end
  369. ```
  370. ```bash
  371. $ rake test
  372. Run options: --seed 14596
  373. # Running:
  374. ......
  375. Finished in 0.024899s, 240.9735 runs/s, 1204.8677 assertions/s.
  376. 6 runs, 30 assertions, 0 failures, 0 errors, 0 skips
  377. ```
  378. An object is not required to implement all APIs in order to work with
  379. Action Pack. This module only intends to provide guidance in case you want all
  380. features out of the box.
  381. ### SecurePassword
  382. `ActiveModel::SecurePassword` provides a way to securely store any
  383. password in an encrypted form. On including this module, a
  384. `has_secure_password` class method is provided which defines
  385. an accessor named `password` with certain validations on it.
  386. #### Requirements
  387. `ActiveModel::SecurePassword` depends on the [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'),
  388. so include this gem in your Gemfile to use `ActiveModel::SecurePassword` correctly.
  389. In order to make this work, the model must have an accessor named `password_digest`.
  390. The `has_secure_password` will add the following validations on the `password` accessor:
  391. 1. Password should be present.
  392. 2. Password should be equal to its confirmation.
  393. 3. This maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends)
  394. #### Examples
  395. ```ruby
  396. class Person
  397. include ActiveModel::SecurePassword
  398. has_secure_password
  399. attr_accessor :password_digest
  400. end
  401. person = Person.new
  402. # When password is blank.
  403. person.valid? # => false
  404. # When the confirmation doesn't match the password.
  405. person.password = 'aditya'
  406. person.password_confirmation = 'nomatch'
  407. person.valid? # => false
  408. # When the length of password, exceeds 72.
  409. person.password = person.password_confirmation = 'a' * 100
  410. person.valid? # => false
  411. # When all validations are passed.
  412. person.password = person.password_confirmation = 'aditya'
  413. person.valid? # => true
  414. ```