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:
- Throw out the virtual attribute, and get rid of the whole
after_save
callback stuff - Throw away the
user_prices
method in your model - Change the
all_dates
attribute name back topurchase_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
Regex Match Everything Up to First Period
Redis or Mongo for Determining If a Number Falls Within Ranges
Overriding the == Operator in Ruby
Creating a Thread-Safe Temporary File Name
Rails 3.1 with Postgresql: Group by Must Be Used in an Aggregate Function
Getting Viewable Text Words via Nokogiri
Devise Custom Messages When Validation Fails
Preserve Newline in Text Area with Ruby on Rails
Ruby: Conditional Matrix? Case with Multiple Conditions
Cannot Use Ruby-Debug19 with 1.9.3-P0