How to Add a Virtual Attribute to a Model in Ruby on Rails

How to add a virtual attribute to a model in Ruby on Rails?

In your model, you can write attribute accessors (reader/writer) for your virtual attribute palindrome attribute this way:

# attr_reader
def palindrome
self[:palindrome]
end

# attr_writer
def palindrome=(val)
self[:palindrome] = val
end

# virtual attribute
def palindrome
#return true/false
end

And, as you are using Rails 4, you have to whitelist palindrome attribute like any other model attribute in your strong param definition inside your controller in order to able to mass assign the value of palindrome. Something like this:

# your_controller.rb
private

def your_model_params
params.require(:message).permit(:palindrome)
end

Take a look at this RailsCast on Virtual Attributes. Although, it's a bit old, but would be useful for concepts.

Note:

A virtual attribute will not show up in the param list automatically. But, you should be able to access it via Rails console like this: Message.new.palindrome. Also, you can expose this virtual attribute in your JSON API, for example if you are using Active Model Serializer, you can have: attribute palindrome in your MessageSerializer and then palindrome will be exposed to the JSON API.

How do I set a virtual attribute on an existing model?

I think, you have a problem somewhere else. It's not easy to find out what's going wrong with your code without seeing it, but this code itself works without any errors:

class User < ActiveRecord::Base
def username=(value)
@username = value
end

def username
@username
end
end

user = User.new(:first_name => "Bill", :last_name => "Gates")
user.username = "billgates"
user.username

Or you can use attr_accessor to replace manual definition of getter and setter:

class User < ActiveRecord::Base
attr_accessor :username
end

Take a look on this screencast to clarify things: http://railscasts.com/episodes/16-virtual-attributes

How to set a virtual attribute in model with Rails?

After reload, my code actually works in console : user.valid? => false (thanks surendran). But my initial problem was in my tests : I thought I could not set virtual attributes because of the error message "undefined method `strip!' for nil:NilClass". But I forgot I test if my user is valid when password is nil, nearly like this :

before { user.password = nil) }
it { should_not be_valid }

before_validation comes before this test so he tries to strip a nil object.

Set Virtual Attribute Rails Active Model

You can use something like this

class Quote
include ActiveModel::Model
attr_accessor :name, :first, :last, :city

def initialize(attributes={})
super
assign_name(name)
end

def assign_name(name)
title_split = name.split(" / ")
self.first = title_split[0]
self.last = title_split[1]
end
end

Also link to documentation here

How to add to the form a virtual fields

First set up your form as usual:

<%= form_for @user do |f| %>
<ol>
<li>
<%= f.label :first_name, 'First Name' %>
<%= f.text_field :first_name %>
</li>
<li>
<%= f.label :last_name, 'Last Name' %>
<%= f.text_field :last_name %>
</li>
<%= f.submit %>
</ol>
<% end %>

In this example we are adding a virtual attribute for first_name and last_name.

user.rb

class User < ActiveRecord::Base
attr_accessor :first_name
attr_accessor :last_name
end

Add an attr_accessor for your new virtual attribute.

users_controller.rb

def create
@user = User.new(:full_name => {'firstname' => user_params[:first_name], 'lastname' => user_params[:last_name]})
...
end

private

def user_params
params.require(:user).permit(:first_name, :last_name)
end

Finally, add a method to permit the virtual attribute params (assuming Rails 4).

To save multiple inputs and save them into a single field in the DB you can combine the virtual fields in the controller and then save them to the DB, as shown in the create method above.

Is is possible to return virtual attributes automatically in Rails models?

Not 100% sure I understand if your concern is related to as_json but if so this will work

class Foo
has_one :bar

def bar_name
bar.name
end
def as_json(options={})
super(options.merge!(methods: :bar_name))
end
end

Now a call to @foo.as_json will by default include the bar_name like your explicit example does.

Ugly would not recommend but you could change the inspection of foo e.g. #<Foo id: "abc123", bar_name: "baz"> as follows

class Foo
def inspect
base_string = "#<#{self.class.name}:#{self.object_id} "
fields = self.attributes.map {|k,v| "#{k}: #{v.inspect}"}
fields << "bar_name: #{self.bar_name.inspect}"
base_string << fields.join(", ") << ">"
end
end

Then the "inspection notation" would show that information although I am still unclear if this is your intention and if so why you would want this.

How should I access a virtual attribute from an associated model?

pluck grabs column directly from the database. You are getting this error since your full_name method is not a database column.

Change User.pluck(:full_name, :id) to

User.select(:id, :first_name, :last_name).all.map{|u| [u.full_name, u.id] }

Virtual attributes in rails model

Well, yes, instance variables aren't saved to the database (how could they be? There's no column for them).

Since the title of the question is "Virtual attributes", I'm going to assume that you don't have a quantity column in your database table (if you do, just remove the attr_accessor bit), however you still need to store the quantity somewhere if you want it to persist.

Usually virtual attributes are used when some attribute is not stored directly in the DB, but can be converted from and to an attribute that is. In this case it doesn't look like that is the case, so I can only recommend that you add a quantity column to your database table.

How to update a model's attribute with a virtual attribute?

This is a case of trying to do in a model what is better left to the controller. All you're trying to do here is to auto-assign a certain attribute on creation from a parameter not directly tied to your model. But you're not even passing that extra parameter to the model anywhere - you're creating your model instances from the user_prices parts of the parameter hash, but the user_price sub-hash is not used anywhere. In any case, this is behavior that is more closely related to the view and action taken than the model, so keep it in the controller.

Try this:

  1. Throw out the virtual attribute, and get rid of the whole after_save callback stuff
  2. Throw away the user_prices method in your model
  3. Change the all_dates attribute name back to purchase_date in the form

Then your parameter hash should look like this:

{"user_price"=> { 
"purchase_date(2i)"=>"10",
"purchase_date(3i)"=>"27",
"purchase_date(1i)"=>"2011"
},
"user_prices"=>
{
"0"=>{"product_name"=>"Item1", "store"=>"Apple Store","price"=>"6"},
"1"=>{"product_name"=>"Item2", "store"=>"Apple Store", "price"=>"7"}
}}

All that's left to do is to merge the single user_price attributeS into each user_prices sub-hash in your create_multiple action. Replace the first line in that action with this:

@user_prices = params[:user_prices].values.collect do |attributes| 
UserPrice.new(attributes.merge(params[:user_price]))
end


Related Topics



Leave a reply



Submit