Accept Rails model attribute only if it was previously blank - ruby-on-rails

I have a Rails model (persisted with Mongoid) that can be collaboratively edited by any registered user. However, I want to allow editing any particular attribute only if it was previously blank or nil.
For example, say someone created an object, and set its title attribute to "Test Product". Then another user comes along and wants to add a value for price, which until now has been nil.
What's the best way to do this, while locking an attribute that has previously been entered?

Look into the ActiveRecord::Dirty module for some nice utility methods you can use to do something like this:
NON_UPDATABLE_ATTRIBUTES = [:name, :title, :price]
before_validation :check_for_previously_set_attributes
private
def check_for_previously_set_attributes
NON_UPDATABLE_ATTRIBUTES.each do |att|
att = att.to_s
# changes[att] will be an array of [prev_value, new_value] if the attribute has been changed
errors.add(att, "cannot be updated because it has previously been set") if changes[att] && changes[att].first.present?
end
end

The easiest way, i think, is by checking for it in the form itself.
Just say add :disabled => true to the input field if the person cannot edit it.
<% if #my_object.name %>
<%= f.text_field :name, :disabled => true %>
<% else %>
<%= f.text_field :name, :disabled => true %>
<% end %>
(i think there is a prettier way to write this code)
But by using this the user has a visual feed back that he can't do something, it is always better to not allor something than to give an error message

Related

Do form validation that does not use database

I have a form and I have made some inputs required. After submitting the form that value will be sent to an API. I know that the validations are put into model file but since I do not have a database, how can I use the rails validation?
Right now I am validating the code inside a controller using if else.
if !params[:groups][:name].blank? && !params[:groups][:make].blank? && !params[:groups][:model].blank? && !params[:groups][:firmware].blank?
This does the work but it is not very elegant.
Take a look at ActiveModel, it lets you do "model things" without the database. There were some limitations that made me not use it in the end (I think related to associations) but for simple stuff it's great (and it's a part of how ActiveRecord works.
Example code from docs
class Person
include ActiveModel::Model
attr_accessor :name, :age
validates :name, :age, presence: true
end
this is easy. On the form input fields that you NEED, add required: true For example:
<%= form.for #something do |f| %>
<%= f.text_field :title, placeholder: 'Title', required: true %>
<% end %>
The user gets an error if the required fields are not filled out correctlty.
Is this what you mean?
Justin
EDIT
I guess I would look at using the gem
client_side_validations
Let us know how you go

Rails - Combining Variables and Saving them in the Controller

I have a "Word" model that has 3 string variables: "word_a" , "word_b" , "word_ab".
A form in my view collects the values for "word_a" and "word_b":
<%= f.text_field :word_a %>
<%= f.text_field :word_b %>
What is the best way to save the value for "word_ab" which will be made automatically from a combo of "word_a" and "word_b"?
The way Im doing it now seems really really wrong. After submitting the first form, I have the controller redirect to another edit page with a 'word_ab' form that has a value that combines 'word_a' and 'word_b'.
<%= f.text_field :word_ab, :value => #word.word_a+"_"+#word.word_b %>
The user then has to resubmit the form to save 'word_ab' into the database. Can't I do this is a controller?
Are you sure you want to save concated value to db? What about editing?
If you still want to do it - best idea is to add attr_accessor in model
attr_accessible :word_a, :word_b
attr_accessor :word_a, :word_b
First line allows yo perform mass assign, second one creates setter and getter methods.
Then, still in a model do
before_validation(:on => :create) do
self.word_ab = word_a + word_b
end
You may perform this on before_save as well, and you may validate word_a and word_b separately with regular validators.
Pro tip: create getter method that returns concated string
def word_ab
self.word_a + self.word_b
end
Associated models
class User
has_one :profile_picture
def word_ab
self.profile_picture.url + self.word_a + self.word_b
end
end

Validate field is unique compared to another field in same form

Say I have two fields in a new or edit form:
<%= f.text_field :email %>
<%= f.text_field :parent_email %>
How, in my model, can I validate that parent_email is different from email? The exclusion option seems like it might work, but I can't figure out how to access the email field's value within the model. Do I need to implement this in the controller instead?
validates :parent_email, exclusion: self.email # doesn't work, nor does :email
The following should work (but I guess there are cooler solutions out there):
class User
validate :email_differs_from_parent_email
private
def email_differs_from_parent_email
if email == parent_email
errors.add(:parent_email, "parent_email must differ from email")
end
end
end

Validating field's presence fails even though the field is not blank

I'm trying to fill out an array with values from checkboxes. It works just fine when creating a record, but fails validation when editing. The params look right, which is what really confuses me:
"record"=>{... "type_array"=>["accounting"], ...}
It looks the same as the params from creating a new record. The fields in New.html.erb and Edit.html.erb also use the same markup.
Edit.html.erb
<div class="field">
<%= f.label :type_array, "What type of record?" %><br />
<% ["accounting", "agriculture", "automotive"].each do |type| %>
<%= check_box_tag 'record[type_array][]', type, (true if #record.type_list.include? type),
:id => type %>
<%= label_tag type, type.titleize, :class => type %><br />
<% end %>
</div>
Parts of Record.rb
validates :type_array, :presence => true
attr_accessor :type_array
attr_accessible :type_array
before_validation :set_type_list
private
def set_type_list
self.type_list = type_array.join ',' if type_array.present?
end
Am I missing something? When I remove the type_array validation and fill out the form, it acts like type_array is empty. Somewhere along the line, it must be lost or something.
I appreciate any help.
(Sidenote: if anyone has a better way to do the list of checkboxes, let me know)
Delete the line attr_accessor :type_array.
This creates accessor methods to a new instance variable, not to the model attribute type_array, which means that #record.type_array now refers to that instance variable instead of the attribute.
You almost never use attr_accessor or it's siblings attr_reader and attr_writer in Rails because you want to deal with model attributes, not instance variables.
Edit: You're using type_array as a virtual attribute.
class Record < ActiveRecord::Base
validates :type_array, :presence => true
attr_accessible :type_array
def type_array=(val)
self.type_list = val.join ','
end
def type_array
self.type_list.split ','
end
def type_array_before_type_cast
type_array
end
end
For the reason why you need that last function definition, see this question.

Accessing a parameter within a plugin

I'm trying to modify the vestal_versions plugin to accept a parameter I set upon saving. This parameter will act as a new flag to determine when to create a revision upon updating. Currently it will always run upon update when a new revision is needed. Here is the affected area of unmodified plugin code:
after_update :create_version, :if => :needs_version?
def create_version
versions.create(:changes => changes.slice(*versioned_columns), :number => (last_version + 1))
reset_version
end
The parameter I am sending in the view upon submit is "forcerevision=n". How would I pull in this parameter and what conditional would I use to allow this to only run when "forcerevision=y"? It seems it would be cleanest to modify the after_update filter?
Here is the log of the data being passed on update.
Processing NotesController#update (for 521.0.0.1 at 2009-12-05 13:25:45) [PUT]
Parameters: {"authenticity_token"=>"#########################+k=", "id"=>"4", "forcerevision"=>"n", "note"=>{"notebook_id"=>"", "public"=>"n", "body"=>"A versioned note", "title"=>"Version Note Test", "flag"=>"important", "star"=>"false", "tag_list"=>""}}
vestal_versions on Github
The cleanest way to do this would be to add an attr_accessor when a model is declared to be versioned.
Then override needs_version? to check that attribute.
Anywhere in LaserLemon::VestalVersions::ClassMethods#versioned add this line:
attr_accessor :force_reversion
Then rewrite LaserLemon::VestalVersions::InstanceMethods#needs_version? to check this attribute:
N.B. due to the way checkboxes are handled "0" is considered false for checkboxes, and boolean fields, but is a true value in ruby. So we can't just check if force_reversion does not evaluate to false.
def needs_version?
!(versioned_columns & changed).empty? ||
![nil, "0", 0, false].include?(force_reversion)
end
And you're all set. Just pass any value for force_reversion as if it were a column.
After the above changes with the following model:
class User
#user.update_attributes(:force_reversion => true, :unversioned_column => new_value)
Or in a form:
<%= form_for #user do |f| %>
<%= f.label :force_reversion, "Force New Version" %>
<%= f.check_box :force_reversion %>
... More fields ...
<% end %>

Resources