I have a form that students are using to rank 6 classes from 1 to 6. If they select Math as "1" (the hardest), then I don't want them to be able to select another subject as the hardest. The form will obviously give them the option to select "1" for each subject, but I want to use validations to protect against submission of a form that doesn't follow instructions
This is a snippet from the form
<div class="field">
<%= f.label(:math, "Mathp") %>
<%= f.select:math, 1..6 %> </br>
</div>
<div class="field">
<%= f.label(:french, "French") %>
<%= f.select:french, 1..6 %> </br>
</div>
I was going to use a validation method like this, but I don't think the logic of it works (i.e. it doesn't guard against every possible situation) and it's probably shitty (non-functional) code too, because i'm just learning how to code (actually I've been failing at it for quite a while now). Can you suggest improvements?
validates :rank_favorites
...
def rank_favorites
unless :math != :french && :french != :history && :history != :spanish && :spanish != :art && :art != :physed return false
end
Your rank_favorites is, sadly, way off but ignorance can be fixed through learning. You're just comparing a bunch of symbols and that doesn't do anything useful (at least not as far as you're concerned), you're validator reduces to this:
unless false && false && false && false && false return false
which is equivalent to:
unless false return false
You probably want to use validate :rank_favorites (not validates) and your validator would add error messages instead of simply return a boolean:
validate :rank_favorites
#...
def rank_favorites
ranks = [math, french, history, spanish, art, physed]
if(ranks.include?(nil))
errors[:base] << 'Rank all of them you lazy person!'
elsif(ranks.uniq.length != ranks.length)
errors[:base] << 'You fail at ranking, no duplicates allowed.'
end
end
The Array#uniq method will produce a copy of your array with the duplicates removed, if the lengths don't match then something was removed and you had duplicate entries.
You might want to spend some time reading the validations guide:
Active Record Validations and Callbacks
You could always do something like this:
validate do
unless [math, french, history, spanish, art, physed].uniq.length == 6
errors.add(:base, :doh_theyre_not_unique_error)
end
end
This really feels like it could use some JS form love though.
so what you really want to do is ensure no subject gets the same ranking :)
:math != :french # => true ALWAYS because they internalized constant strings which are obviously different
If you did..
self.math != self.french # => this is comparing variables now. much better. BUT Still wrong in terms of the logic you want
How about
if [self.math,self.french,self.history,self.spanish,self.art,self.physed].uniq.sort != [1,2,3,4,5,6])
errors.add(:base,"Repeated rankings")
end
Related
In my communication table I have some columns:
id | UserID | CommunicationMode | CommunicationDetail | Private
1 | 1 | Phone | 123456789 | 1
2 | 1 | Email | abc#abc.com | 1
And I want to update column value using where clause using loop like below:
create
#user_communication=Communication.where(:UserID => current_user.id)
if !#user_communication.blank?
#user_communication.each do |c|
if params[:ChkBx_Phone].to_i == 1
c.where("CommunicationMode == 'Phone'").update_attribute( :Private, "1")
elsif params[:ChkBx_Phone].to_i == 0
c.where("CommunicationMode == 'Phone'").update_attribute( :Private, "0")
end
if params[:ChkBx_Email].to_i == 1
c.where("CommuicationMode == 'Email'").update_attribute( :Private, "1")
elsif params[:ChkBx_Email].to_i == 0
c.where("CommunicationMode == 'Email'").update_attribute( :Private, "0")
end
end
end
end
I want to check above that if Phone checkbox is checked then it updates Private column with value 1 else 0 where CommunicationMode is Phone and for email I want to check that if Email checkbox is checked then it updates Private column with value 1 else 0 where CommunicationMode is Email
And below is Phone and Email checkboxes:
<table>
<% #user_communication.each do |c| %>
<tr>
<td>
<% if c.CommunicationMode == "Phone" and c.Private.to_s == "1" %>
<input type="checkbox" name="ChkBx_Phone"
id="ChkBx_Phone" value="1" checked = "checked">
<%= label(:lb_Phone, "Phone") %>
<% elsif c.CommunicationMode == "Phone" and c.Private.to_s == "0" %>
<%= check_box_tag 'ChkBx_Phone' %>
<%= label(:lb_Phone, "Phone") %>
<% end %>
</td>
</tr>
<tr>
<td>
<% if c.CommunicationMode == "Email" and c.Private.to_s == "1" %>
<input type="checkbox" name="ChkBx_Email"
id="ChkBx_Email" value="1" checked = "checked">
<%= label(:lb_Email, "Email") %>
<% elsif c.CommunicationMode == "Email" and c.Private.to_s == "0" %>
<%= check_box_tag 'ChkBx_Email' %>
<%= label(:lb_Email, "Email") %>
<% end %>
</td>
</tr>
<% end %>
</table>
But I am getting an error below:
undefined method `where' for #<Communication:0x4bc5490>
But when I am using below code:
create
#user_communication=Communication.where(:UserID => current_user.id)
if !#user_communication.blank?
#user_communication.each do |c|
if params[:ChkBx_Phone].to_i == 1
puts "Hassan2"
c.update_attribute( :Private, "1")
elsif params[:ChkBx_Phone].to_i == 0
puts "Ali2"
c.update_attribute( :Private, "0")
end
end
end
end
Its working fine but it update both Private column value of Phone and Email and I have checked only Phone checkbox.
Kindly suggest me, waiting for your reply.
Thanks.
As Kingston said, when you iterate through the #user_communication collection, the c variable in the block is in fact a concrete Communication object, not an ActiveRecord::Relation object, which is what contains the .where query methods.
Additionally, there are a couple of other problems I have noticed. Some, I cannot directly help you solve because I don't know how your system works, but I will point them out for you and try to suggest an alternative, and hopefully you can figure out the best way to fix them in the context of your system's requirement.
In regards to the create method, there are two approaches that will have the same end result, but one of them I would consider the "naïve approach". I'm going to show the naïve approach only to give you a direct alternative/answer to the first create method you posted, and so you can see the difference and figure out the best solution:
Naïve Approach:
def create
#user_communication=Communication.where(:UserID => current_user.id)
# Transaction so we don't execute X number of individual update statements
Communication.transaction do
#user_communication.each do |c|
if c.CommunicationMode == "Phone"
# Note: Its not necessary to use the ternary operator if you're not
# comfortable with it, however it can save you a few lines of code.
c.update(Private: (params[:ChkBx_Phone].to_i == 1 ? "1" : "0") )
elsif c.CommunicationMode == "Email"
c.update(Private: (params[:ChkBx_Email].to_i == 1 ? "1" : "0") )
end
end # end communication loop
end # end transaction
end
This will iterate through the the Communication objects for that user id, and update each one based on its CommunicationMode value, setting it to a 1 or a 0, depending on the value of the two checkboxes. What you will see in your log are several individual UPDATE statements for each Communication object you have in the collection. Normally, the database will execute the UPDATE statement immediately, since the updates are wrapped in their own transactions, and consequently, this becomes rather costly over time if you have a large number of records. So to avoid that problem, as you can see I've wrapped the entire operation in a transaction, and consequently, the updates are only committed at the end; this is much more efficient (you can do some experiments to verify this yourself; you should be able to see a noticeable time difference with and without the transaction wrapper).
And now, the "potentially" better approach:
The "Potentially" better / less naïve approach
... other method code
Communication.where(UserID: current_user.id, CommunicationMode: "Phone").update_all(Private: (params[:ChkBx_Phone].to_i == 1 ? "1" : "0") )
Communication.where(UserID: current_user.id, CommunicationMode: "Email").update_all(Private: (params[:ChkBx_Email].to_i == 1 ? "1" : "0") )
This will execute only two UPDATE statements (which you can also wrap in a transaction if you like). This should execute even quicker than the above approach, and saves you even more lines of code.
Note that both this and the naïve approach can be moved to a method in the Communication model, to keep heavy model-related operations out of the controller.
Another issue of note
In your view code, you appear to be iteration over a collection #user_communication, within which you're creating a checkbox for each object:
<input type="checkbox" name="ChkBx_Email" id="ChkBx_Email" value="1" checked = "checked">
Consequently, if you have five objects in that collection, you'll see a list of five checkboxes. Because of the way you've named this input, only one of the values is ever being sent (I believe it is typically the "last" input in the DOM). In other words, if you click "on" the first checkbox, but leave the last one "off", the value of the checkbox will be "off". I have a strong feeling this is probably not how you want your system to behave. Perhaps you will want to place those checkboxes outside of the loop (above), since they don't appear to have anything to do with the Communication objects themselves.
Additionally, an unchecked checkbox is never sent to the server. The line params[:ChkBx_Phone].to_i == 1 will crash if you unchecked that checkbox because params[:ChkBx_Phone] is nil, and you'd be invoking to_i on a nil object. This answer shows you an alternative for this solution. In your case, you will want to make sure of the Rails helper tags, such as check_box_tag, and hidden_field_tag, like so:
<%=hidden_field_tag 'ChkBx_Phone', '0'%>
<%=check_box_tag 'ChkBx_Phone', '1', true %>
As a result, if the checkbox is unchecked, because of the existence of the identically named hidden field input, the server will always receive a value for this parameter.
In your code
#user_communication.each do |c|
# Your logic
end
you looping #user_communication then c variable will contain Communication object not Active record relation,then you are doing c.where(),c contain Communication object. But .where Active record relation.so it is throwing error.
I have an answer model which belongs to a question which has a "correct" boolean column. Ideally a question can have only 1 correct answer ( much like the stackoverflow system).
I have the following controller + model code which uses a toggle_correct method to toggle the "correct" boolean value in the view (all of which works nicely).
When i try and create a new answer the one_correct_answer validation error is raised even though the correct column is set to default: false in the migration and the value is set to 0 (false) in the application POST trace
How can I amend my code so that a this validation only allows there to be 1 correct answer per question and doesn't interrupt the creation of a new an answer object?
answer.rb
validate :one_correct_answer
def one_correct_answer
answers = self.question.answers.map(&:correct)
errors.add(:user_id, "You can't have more than 1 correct answer #{answers}") if answers & [true]
logger.debug("Answers array #{answers}")
end
def toggle_correct(attribute)
toggle(attribute).update_attributes({attribute => self[attribute]})
end
answers_controller.rb
def correct
#answer = Answer.find(params[:id])
if #answer.toggle_correct(:correct)
respond_to do |format|
format.html { redirect_to :back, notice: "Answer submitted" }
format.js
end
end
end
_answer.html.erb
<div id="correct_answer_<%= answer.id %>" class="<%= answer.correct == true ? 'green-tick' : 'default-tick' %>">
<% if answer.question.user == current_user %>
<%= link_to "✓", correct_answer_path(answer), id: "tick", class: "correct_#{answer.id}", remote: true, method: :put %>
<% else %>
<% if answer.correct == true %>
<div id="tick", class='correct_<% answer.id %>'> ✓</div>
<% end %>
<% end %>
</div>
The reason it will fail is that you are adding an error if any answer associated to the question is correct. And you test this even though the answer you are trying to save is correct or not. So the first thing you should do is to only check if there are any correct answers if the answer you are trying to save is indeed correct, like this:
validate :one_correct_answer, if: :correct?
This way, the method one_correct_answer will only be validated if the current answer is correct.
However, you still have one additional problem. If the answer you are trying to save is correct, then the method will be called and it will add an error if there is any answer that is correct... which it will probably be since the current answer should also be listed in that association. So what you want to do is to check if there is an additional answer that is correct.
So in the end, I would probably end up validating it like this instead:
validates_uniqueness_of :correct, scope: :question_id, if: :correct?
What this will do is that it validates the unique combination of the question_id column and the correct column, but only if correct is true. That makes it so that you can have multiple false but only one true correct column per question.
Your problem is probably here:
errors.add(:user_id, "You can't have more than 1 correct answer #{answers}") if answers & [true]
answers & [true] will always return an array(since answers is an array), and blank arrays are true values in Ruby.
Even if they were false values, your condition wouldn't work, since there has to be one correct answer, and your condition would check that there are none.
I would use this condition:
self.question.answers.count(&:correct) <= 1
When you set a validation message in paperclip, such as
validates_attachment_presence, :image, :message => 'xxxx'
The custom message comes automatically prefixed with the name of the field, even though it has been overwritten with the :message . How do you totally override the message and make it totally custom?
Edit: typo
Not a real solution but a Easy one is to skip paperclip validation and write custom one.
validate :check_content_type
def check_content_type
if !['image/jpeg', 'image/gif','image/png'].include?(self.image_content_type)
errors.add_to_base("Image '#{self.image_file_name}' is not a valid image type") # or errors.add
end
end
I hope it can help
You actually want to do this inside your view rather than your model and it's actually quite straight forward. We're just going to loop through the errors, and when the one for your attachment comes up we'll ignore the field name:
<ul>
<% #myObject.errors.keys.each do |field| %>
<% #myObject.errors[field].each do |msg| %>
<% if field == :image_file_name %>
<li><%= msg %></li>
<% else %>
<li><%= field.to_s + " " + msg %></li>
<% end %>
<% end %>
<% end %>
</ul>
Replacing #myObject with the name of your model that should display only the message set to your attachment validation errors. This is just a simple example that displays them inline with the rest, but of course you could do anything you like with the messages. It's important to keep the name of the field that had the error in case you want to program any logic thats specific to its failure without having to rely on the error message staying exactly the same forever.
It's standard Rails behavior to show include the attribute name before the validation errors. You have a few options to work around this behavior:
Make your error message OK to have the attribute name prepended :)
Use a different error message formatter. It's pretty easy to write your own helper to iterate through an #object.errors and wrap messages in HTML tags. I prefer to use the error messages in-line near the fields so we always skip the attribute name.
Custom validation which adds the errors to base. This is easy, but wrong, since you're suggesting there's a validation error on a field. Still may solve your problem.
Override humanized_attribute_name for that attribute to hide it. humanized_attribute_name is probably used elsewhere, so this may cause other issues.
.
HumanizedAttributesOverride = {
:image => ""
}
def self.human_attribute_name(attr)
HumanizedAttributesOverride[attr.to_sym] || super
end
I don't know if it's just a typo inside your question, but it should be:
validates_attachment_presence :image, :message => 'xxxx'
And I would not use :message to add a custom error message, but put it inside a locales file.
I'm new to rails after moving from PHP and am having no end to the frustrations, but hopefully there is a steep learning curve.
I was following a guide on how to make a twitter clone in rails, and have continued along that path making it more and more twitter like.
So I've got a 'users' page /users/show.html.erb which show all the posts from a user.
Now, if the currently logged in user is the same as the page owner, I'm trying to show the text box so that the user can add a new entry.
I've got what should be a very simple
<% if params[:id] == session[:user_id] %>
put the text box here
<% end %>
of course, that isn't working, but right above it I've output both the session[:user_id] and the params[:id], and the printout is exactly the same.
If I set the == to !=, I get the 'put the text box here' message.
any suggestions as to what I'm doing wrong?
I know these two values match, as I can see in the url and the output of the currently logged-in user. I've also output
-<% session[:user_id] %>-
-<% params[:id] %>-
so that I can see there is no gaps or spaces or other characters on either end of the parameters, and it all looks clean.
The output looks like this
-4c4483ae15a7900fcc000003-
-4c4483ae15a7900fcc000003-
which is the mongodb objectId of the user with dashes on either side to show that there are no spaces or anything.
Are you sure that both items are simple strings? What happens if you run, say
params[:id].to_s == session[:user_id].to_s
?
It could be like jasonpgignac pointed out. If you enter into IRB:
num = "7"
num2 = 7
num == num2 # => false
Make sure they are both of the same type. Putting <%= num2 %> will actually trigger the .to_s method... hence why the two appear to be equal when you output them in your .erb page.
Also, you might want to move that comparison into the controller. Something like:
#is_user_home = params[:id].to_s == session[:user_id].to_s
Then you can put in your view:
<% if #is_user_home %>
code here
<% end %>
It makes the code a little more easier to read.
As it was already stated, in most cases this kind of problems are caused by mismatch of compared types. Just adding to_s to both variables solves most cases.
Here check great source to learn more about all kind of comparisons used in Ruby.
I want to show a post author's name; <% #post.author.name %> works unless author is nil. So I either use unless #post.author.nil? or add a author_name method that checks for nil as in <% #post.author_name %>. The latter I try to avoid.
The problem is that I may need to add/remove words depending on whether there is a value or not. For instance, "Posted on 1/2/3 by " would be the content if I simply display nil. I need to remove the " by " if author is nil.
Null object pattern is one way to avoid this. In your class:
def author
super || build_author
end
This way you will get an empty author no matter what. However, since you don't actually want to have an empty object sometimes when you do expect nil, you can use presenter of some kind.
class PostPresenter
def initialize(post)
#post = post
end
def post_author
(#post.author && #post.author.name) || 'Anonymous'
end
end
Another way is using try, as in #post.author.try(:name), if you can get used to that.
You can use try:
<%= #post.author.try(:name) %>
It will attempt to call the name method on #post.author if it is non-nil. Otherwise it will return nil, and no exception will be raised.
Answer to your second question: In principle there is nothing wrong with the following:
<% if #post.author %>
written by <%= #post.author.name %>
<% end %>
or
<%= "written by #{#post.author.name}" if #post.author %>
But if this is a recurring pattern, you might want to write a helper method for it.
# app/helpers/authors_helper.rb or app/helpers/people_helper.rb
class AuthorsHelper
def written_by(author)
"written by #{author.name}" if author
end
end
# in your views
<%= written_by(#post.author) %>
Write a method which accepts any variable and checks to see if it is nuil first, and if it is not displays it. Then you only have to write one method.
I found your question interesting as I have often come across similar situations, so I thought I'd try out making my first Rails plugin.
I'm afraid I haven't put in any tests yet but you can try it out http://github.com/reubenmallaby/acts_as_nothing (I'm using Ruby 1.9.1 so let me know if you get any problems in the comments or on Github!)