How to set a default value in a text_field? - ruby-on-rails

I know this question has been asked a zillion times, but I still feel I'm missing something obvious. Given a model Address with a field city which we want to be initialized with a default value, say Awesome City.
What I've tried so far:
1) Default value in the view:
# #file: /app/views/addresses/_form.html.erb
<dt><%= f.label :city %></dt>
<dd><%= f.text_field :city, :value => 'Awesome city' %></dd>
This works but doesn't feel right. Also the user cannot change it, the value will still be Awesome city.
2) Overwriting the city method:
# #file: app/models/address.rb
def city
self[:city] ||= 'Awesome city'
end
The field is rendered empty, so for some reason it doesn't work. If you try Address.new.city you get the default value though.
3) Assign the default value in the controller:
# address doesn't have a controller, we use nested forms
# belongs_to :parent
# #file: /app/controllers/parents_controller.rb
def new
#parent = Parent.new
#parent.build_address
#parent.address.city = 'Awesome city'
end
This actually works but I don't think it should be in the controller, rather in the model.
4) Adding an after_initialize call (yes, it works, you don't have to overwrite the callback):
# #file: app/models/address.rb
after_initialize :set_default_city
def set_default_city
self.city = 'Awesome city'
end
This works as well, but this will be called every time an object is instantiated as well, which we don't want.
5) JavaScript, this seems like an extreme measure, it should be easier than this.
Bottom line: what's the best way to add a default value? Everytime I have to give a field a default value it seems I'm trying to hard for such a simple thing. Are there other options which I'm missing?
Edit:: I'm using Rails 3.1.4 with Ruby 1.8.7/1.9.2

You can define it in your migration file:
class CreateAddress < ActiveRecord::Migration
def change
create_table :addresses do |t|
...
t.string :city, default: "Awesome City"
...
t.timestamps
end
end
end
Other option is to define it in the new action, in your controller, which is more flexible... So, when you create a new address it will be started with the default value....
def new
#address = Address.new
#address.city ||= "Awesome City"
...
end
EDIT - possible solution to define default value in the model:
before_save :set_default_city
def set_default_city
self.city ||= 'Awesome city'
end

My ruby is a little rusty but something like
Parent < ActiveRecord::Base
def self.custom_parent(options)
custom_parent = self.new
if options.empty?
custom_parent.update_attributes(default_options)
else
//Iterate over all options and assign them to custom_parent
end
return custom_parent
end
end
you have to create the "default_options" hash
I don't think this will run out of the box but I think you got my thought

Related

Associated param not going through, but others will? Why?

Issue: Specific record from associated model won't pass through on create, but others from the same associated model will....
I have the following in my create for UserProducts:
def create
#user_product = UserProduct.new(user_product_params)
#product = Product.find_by(params[:product_id])
#user = current_user
#user_product.user_id = #user.id
#user_product.country = #product.country
#user_product.production_price = #product.price
...
private
...
def user_product_params
params.require(:user_product).permit(:product_id, :color, :size, :production_price, {product_ids: []})
end
Models:
Product:
has_and_belongs_to_many :user_products
User Products:
has_and_belongs_to_many :products
The issue I don't get is that #user_product.country = #product.country passes through to my models db perfectly fine. But #user_product.production_price = #product.price won't. Is there a possible interference?
Product model has a t.string "price", and UserProduct has the t.string "production_price"
I attempted putting .to_s at the end of price, didn't work. Tried putting .to_i at the end of price, didn't work either.
If I do user_product.production_price = #product.price + 5 it will go through as 5. So, it's essentially saying production_price is nil when it isn't?
My biggest mind boggle is it passes country through but not price.
In the CMD. I see everything passing through, including :country without production_price. No errors, or unpermitted params, etc.

When to use attr:accessors in place of a permanent column in rails?

I'm creating my own website using Ruby on Rails. One thing that I've failed to comprehend is why and when to use attr:accessors in place of a permanent column for a model. For instance, let's say that I created a 'posts' model which would have a title, description and some content associated with it. Now should I do rails g model Post title:string description:text content:text or should I declare them as attr:accessible :title, :description, :content.
I'm not very experienced in rails, so please bear with me if this sounds too silly to you.
You can use attr_accessor if you need virtual attributes in model.
For eg: In contact us form you need not to see form data, but you need to send that data using email. So you can create attr_accessor for adding virtual attributes and can also apply validations on that.
class Contact
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
ref
attr_accessible is to white list of attributes that can be mass assigned in model.
class Comment < ActiveRecord::Base
attr_accessible :user_id, :content
end
def create
#so here params[:comment], have all parameters. But if those params are not in attr_accessible list it will not save it.
# you can try that by remove attr_accessible from model
#comment = Comment.new(params[:comment])
if #comment.save
flash[:notice] = "Successfully created comment."
redirect_to #comment
else
render :action => 'new'
end
end
Comment Form:
<%= form_for #comment do |f| %>
<%= f.error_messages %>
<%= f.hidden_field :user_id, value: current_user.id %>
<p>
<%= f.label :content %><br />
<%= f.text_area :content %>
</p>
<p><%= f.submit %></p>
<% end %>
Happy Coding...
To add to Pardeep's epic answer, you'll want to look at this RailsCast (RE "virtual attributes"):
attr_accessor basically creates a setter & getter method in the model.
Probably doesn't make any sense; what you have to remember is that each Rails model is a class. Classes form the backbone of object-orientated programming.
Since Ruby is object orientated, each time you do anything with the language, it expects classes to be invoked & manipulated. The basis of OOP is to load classes into memory & play with them; good write-up here.
In classic OOP, your classes would be hard-coded with a series of attributes:
class Mario
def jump
pos_y + 5
end
def pos_y
# gets y val from the viewport
end
end
This will allow you to send instructions to the program, in turn modifying the class:
#mario.jump
... this should modify the viewport etc in the way you defined within the class.
--
Rails is very similar to the above, except most of the attributes are defined by ActiveRecord;
#app/models/mario.rb
class Mario < ActiveRecord::Base
# attributes from db include height, weight, color etc
end
Rails models allow you to call:
#mario = Mario.find x
#mario.height = "255"
... however, they don't allow you to create attributes which are stored in memory only.
For example...
#app/models/mario.rb
class Mario < ActiveRecord::Base
attr_accessor :grown
end
The above will give you an instance value of grown, which will allow you to populate this independently of the database.
So say you wanted to...
#mario = Mario.find x
#mario.grown = true if params[:grown]
#mario.height += "150" if #mario.grown
Regarding the difference between attr_accessor and attr_accessible, you'll want to look up Rails 3 and mass assignment.
I came into Rails ~ 4.0, so I didn't have to deal with attr_accessible so much; it was basically the way to permit parameters in Rails 3 models.
In Rails 4/5, you use strong params in the controller:
#app/controllers/mario_controller.rb
class MarioController < ApplicationController
def create
#mario = Mario.new mario_params
#mario.save
end
private
def mario_params
params.require(:mario).permit(:x, :y, :z)
end
end

Rails 4: Assigning a records "has many" attribute without database saving

I have a couple models shown below and I'm using the search class method in Thing to filter records
class Category << ActiveRecord::Base
has_many :thing
end
class Thing << ActiveRecord::Base
belongs_to :category
:scope approved -> { where("approved = true") }
def self.search(query)
search_condition = "%" + query + "%"
approved.where('name LIKE ?', search_condition)
end
end
It works fine in my Things controller. The index route looks like so:
def index
if params[:search].present?
#things = Thing.search(params[:seach])
else
#thing = Thing.all
end
end
On the categories show route I display the Things for this category. I also have the search form to search within the category.
def show
#category = Categories.find(params[:id])
if params[:search].present?
#category.things = #category.things.search()
end
end
So the problem is that the category_id attribute of all the filtered things are getting set to nil when I use the search class method in the categories#show route. Why does it save it to database? I thought I would have to call #category.save or update_attribute for that. I'm still new to rails so I'm sure its something easy I'm overlooking or misread.
My current solution is to move the if statement to the view. But now I'm trying to add pages with kaminiri to it and its getting uglier.
<% if params[:search].present? %>
<% #category.things.search(params[:search]) do |thing| %>
... Show the filtered things!
<% end %>
<% else %>
<% #category.things do |thing| %>
... Show all the things!
<% end %>
<% end %>
The other solution I thought of was using an #things = #categories.things.search(params[:search]) but that means I'm duplicated things passed to the view.
Take a look at Rails guide. A has_many association creates a number of methods on the model to which collection=(objects) also belongs. According to the guide:
The collection= method makes the collection contain only the supplied
objects, by adding and deleting as appropriate.
In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id]).
Like Yan said, "In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id])". Validations will solve this problem.
Records are being saved as nil because you have no validations on your model. Read about active record validations.
Here's the example they provide. You want to validate presence as well because records are being created without values.
class Person < ActiveRecord::Base
validates :name, presence: true
end
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false

Creating a Rails change log

I am pretty new to rails (and development) and have a requirement to create a change log. Let's say you have an employees table. On that table you have an employee reference number, a first name, and a last name. When either the first name or last name changes, I need to log it to a table somewhere for later reporting. I only need to log the change, so if employee ref 1 changes from Bill to Bob, then I need to put the reference number and first name into a table. The change table can have all the columns that mnight change, but most only be populated with the reference number and the changed field. I don't need the previous value either, just the new one. hope that makes sense.
Looked at gems such as paper trail, but they seem very complicated for what I need. I don't ever need to manipulate the model or move versions etc, I just need to track which fields have changed, when, and by whom.
I'd appreciate your recommendations.
If you insist on building your own changelog, based on your requirements you can do so using a few callbacks. First create your log table:
def up
create_table :employee_change_logs do |t|
t.references :employee
# as per your spec - copy all column definitions from your employees table
end
end
In your Employee model:
class Employee < ActiveRecord::Base
has_many :employee_change_logs
before_update :capture_changed_columns
after_update :log_changed_columns
# capture the changes before the update occurs
def capture_changed_columns
#changed_columns = changed
end
def log_changed_columns
return if #changed_columns.empty?
log_entry = employee_change_logs.build
#changed_columns.each{|c| log_entry.send(:"#{c}=", self.send(c))}
log_entry.save!
end
end
I recommend the gem vestal_versions.
To version an ActiveRecord model, simply add versioned to your class like so:
class User < ActiveRecord::Base
versioned
validates_presence_of :first_name, :last_name
def name
"#{first_name} #{last_name}"
end
end
And use like this:
#user.update_attributes(:last_name => "Jobs", :updated_by => "Tyler")
#user.version # => 2
#user.versions.last.user # => "Tyler"
The first thing we did was put an around filter in the application controller. This was how I get the current_employee into the employee model, which was the challenge, especially for a newbie like me!
around_filter :set_employee_for_log, :if => Proc.new { #current_account &&
#current_account.log_employee_changes? && #current_employee }
def set_employee_for_log
Thread.current[:current_employee] = #current_employee.id
begin
yield
ensure
Thread.current[:current_employee ] = nil
end
end
end
Next, in the employee model I defined which fields I was interested in monitoring
CHECK_FIELDS = ['first_name', 'last_name', 'middle_name']
then I added some hooks to actually capture the changes IF logging is enabled at the account level
before_update :capture_changed_columns
after_update :log_changed_columns, :if => Proc.new { self.account.log_employee_changes? }
def capture_changed_columns
#changed_columns = changed
#changes = changes
end
def log_changed_columns
e = EmployeeChangeLog.new
Employee::CHECK_FIELDS.each do |field|
if self.send("#{field}_changed?")
e.send("#{field}=", self.send(field))
end
end
if e.changed?
e.update_attribute(:account_id, self.account.id)
e.update_attribute(:employee_id, self.id)
e.update_attribute(:employee_ref, self.employee_ref)
e.update_attribute(:user_id, Thread.current[:current_employee])
e.save
else return
end
end
And that;s it. If the account enables it, the app keeps an eye on specific fields and then all changes to those fields are logged to a table, creating an simple audit trail.

No foreign key in new scaffold

Good day... I have huge trouble with this and it drives me insane.
My user creation should have some additional data, like this:
<div class="field"><%= label_tag 'species', %>
<%= f.collection_select :species_id, Species.all, :id, :name %></div>
It displays the list of species correctly, yes, but when I do a submit, it is utterly ignored. Not even the number appears in the table of the database, it just disappears. I have put the correct associations:
class Species < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
# ... Other stuff
belongs_to :species
# ... Other stuff
end
I have also tried manipulating the controller:
class UsersController < ApplicationController
def create
logout_keeping_session!
#user = User.new(params[:user])
#user.species = Species.find(params[:species_id])
# Other stuff
end
end
But that only gives me 'Cannot find Species without id' even though the params array contains an element 'species_id' with a value.
I am at the end of my wits. Quite new to this, but is this RESTful? Not to find out how to get things done that seem easy? I love Rails and would like to continue.
Thanks for listening
your find fails because the params is probably: params[:user][:species_id] but if it is there like it is supposed, it should be set already, too.

Resources