{"action":"created","issue":{"url":"https://api.github.com/repos/rails/rails/issues/25787","repository_url":"https://api.github.com/repos/rails/rails","labels_url":"https://api.github.com/repos/rails/rails/issues/25787/labels{/name}","comments_url":"https://api.github.com/repos/rails/rails/issues/25787/comments","events_url":"https://api.github.com/repos/rails/rails/issues/25787/events","html_url":"https://github.com/rails/rails/issues/25787","id":164935847,"number":25787,"title":"Attributes API with virtual attributes don't work in form builders without a value?","user":{"login":"cannikin","id":300,"avatar_url":"https://avatars.githubusercontent.com/u/300?v=3","gravatar_id":"","url":"https://api.github.com/users/cannikin","html_url":"https://github.com/cannikin","followers_url":"https://api.github.com/users/cannikin/followers","following_url":"https://api.github.com/users/cannikin/following{/other_user}","gists_url":"https://api.github.com/users/cannikin/gists{/gist_id}","starred_url":"https://api.github.com/users/cannikin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cannikin/subscriptions","organizations_url":"https://api.github.com/users/cannikin/orgs","repos_url":"https://api.github.com/users/cannikin/repos","events_url":"https://api.github.com/users/cannikin/events{/privacy}","received_events_url":"https://api.github.com/users/cannikin/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/rails/rails/labels/activerecord","name":"activerecord","color":"0b02e1"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":4,"created_at":"2016-07-11T21:13:44Z","updated_at":"2016-07-13T13:22:05Z","closed_at":null,"body":"Playing with virtual attributes via the Attributes API in Rails 5. They don't seem to work in Form Builders unless you initialize the attribute with a value before the form builder gets ahold of it:\r\n\r\n### Steps to reproduce\r\n\r\n```ruby\r\n# app/models/user.rb\r\nclass User < ApplicationRecord\r\n  # first_name, last_name in database\r\n  attribute :title, :string # not in DB\r\nend\r\n\r\n# app/controllers/users_controller.rb\r\nclass UsersController < ApplicationController\r\n  def edit\r\n    @user = User.find(params[:id])\r\n  end\r\nend\r\n```\r\n\r\n```erb\r\n<%# app/views/users/edit.html.erb %>\r\n<%= form_for @user do |f| %>\r\n  <%= f.text_field :first_name %>\r\n  <%= f.text_field :last_name %>\r\n  <%= f.text_field :title %>\r\n<% end %>\r\n```\r\n\r\n### Expected behavior\r\nShould show a form with three input boxes, one each for `first_name`, `last_name` and `title`.\r\n\r\n### Actual behavior\r\n`ActiveModel::MissingAttributeError: missing attribute: title`\r\n\r\nI'm able to get the form to render properly if, in the controller, I set the title to an empty string:\r\n\r\n```ruby\r\nclass UsersController < ApplicationController\r\n  def edit\r\n    @user = User.find(params[:id])\r\n    @user.title = ''\r\n  end\r\nend\r\n```\r\n\r\nI thought maybe I could do this automatically with `attribute :title, :string, :default = ''` but I get the same error. :( Maybe this is expected (and desired?) behavior? I just haven't seen it documented anywhere.\r\n\r\nLast few items in the stack trace:\r\n\r\n```\r\nactiverecord (5.0.0) lib/active_record/attribute_methods/read.rb:66:in `block in _read_attribute'\r\nactiverecord (5.0.0) lib/active_record/attribute_set.rb:42:in `block in fetch_value'\r\nactiverecord (5.0.0) lib/active_record/attribute.rb:196:in `value'\r\nactiverecord (5.0.0) lib/active_record/attribute_set.rb:42:in `fetch_value'\r\nactiverecord (5.0.0) lib/active_record/attribute_methods/read.rb:66:in `_read_attribute'\r\nactiverecord (5.0.0) lib/active_record/attribute_methods/read.rb:36:in `__temp__d6563737167656'\r\nransack (1.7.0) lib/ransack/helpers/form_builder.rb:10:in `value'\r\n```\r\n\r\n### System configuration\r\n**Rails version**: 5.0.0\r\n\r\n**Ruby version**: 2.3.1p112\r\n"},"comment":{"url":"https://api.github.com/repos/rails/rails/issues/comments/232352803","html_url":"https://github.com/rails/rails/issues/25787#issuecomment-232352803","issue_url":"https://api.github.com/repos/rails/rails/issues/25787","id":232352803,"user":{"login":"sled","id":320112,"avatar_url":"https://avatars.githubusercontent.com/u/320112?v=3","gravatar_id":"","url":"https://api.github.com/users/sled","html_url":"https://github.com/sled","followers_url":"https://api.github.com/users/sled/followers","following_url":"https://api.github.com/users/sled/following{/other_user}","gists_url":"https://api.github.com/users/sled/gists{/gist_id}","starred_url":"https://api.github.com/users/sled/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sled/subscriptions","organizations_url":"https://api.github.com/users/sled/orgs","repos_url":"https://api.github.com/users/sled/repos","events_url":"https://api.github.com/users/sled/events{/privacy}","received_events_url":"https://api.github.com/users/sled/received_events","type":"User","site_admin":false},"created_at":"2016-07-13T13:22:05Z","updated_at":"2016-07-13T13:22:05Z","body":"The reason this happens is because the non-database backed attribute is in an uninitialized state.\r\nHere's the logic / trace behind it:\r\n\r\nWhen you retrieve a record from the database, the raw result hash is passed to the [`instantiate`](https://github.com/rails/rails/blob/2797de25141098c501dd32d72af8050997cfe036/activerecord/lib/active_record/persistence.rb#L56) method of `ActiveRecord::Persistence`.\r\n\r\nThis method does not instantiate the model in a normal way e.g using `MyModel.new`, rather it allocates on a lower level and bypasses the constructor, writing directly to the instance variables.\r\n\r\nThe important thing here is that the `attributes` parameter which gets passed to `instantiate` only contains keys and values for the columns in the database. So when the attribute set is built on line [68](https://github.com/rails/rails/blob/2797de25141098c501dd32d72af8050997cfe036/activerecord/lib/active_record/persistence.rb#L68), the [attribute set](https://github.com/rails/rails/blob/2797de25141098c501dd32d72af8050997cfe036/activerecord/lib/active_record/attribute_set/builder.rb#L13) only has values defined for the database columns.\r\n\r\nThis in turn leads to an uninitialized attribute to be returned [here](https://github.com/rails/rails/blob/2797de25141098c501dd32d72af8050997cfe036/activerecord/lib/active_record/attribute_set/builder.rb#L104) here because it has no value for this attribute but the type of it is defined.\r\n\r\nE.g `value_present == false` and `types.key?(name)` is true.\r\n\r\nTL;DR you need to set the attribute to any value before you access it through the getter methods. \r\n\r\nFun fact is though:\r\n\r\n```\r\nclass User < ActiveRecord::Base\r\n   attribute :title, :string\r\nend\r\n```\r\n\r\nScenario 1:\r\n```\r\nuser = User.first\r\nuser.title => ActiveModel::MissingAttributeError: missing attribute\r\nuser.read_attribute(:title) => nil\r\nuser.title => ActiveModel::MissingAttributeError: missing attribute\r\nuser.write_attribute(:title, 'a title') \r\nuser.title => 'a title'\r\n```\r\n\r\nScenario 2:\r\n```\r\nuser = User.first\r\nuser.assign_attributes(title: 'a title')\r\nuser.title => 'a title'\r\n```\r\n\r\nThe workaround I use is to assign a value to custom defined attributes in `after_find`:\r\n\r\n```\r\nclass User < ActiveRecord::Base\r\n  attribute :title, :string\r\n\r\n  after_find do\r\n    # initialize to nil\r\n    values = Hash[custom_attribute_names.map { |attr_name| [attr_name, nil] }]\r\n    assign_attributes(values)\r\n    # clear dirty flag\r\n    clear_attribute_changes(custom_attribute_names)\r\n  end\r\n\r\n  private\r\n\r\n  def custom_attribute_names\r\n    self.class.attribute_names - self.class.column_names\r\n  end\r\nend\r\n```\r\n\r\nI admit, it's not the most elegant solution but it works fine in my case. "}}
{"actor":{"display_login":"sled"}}