update_attribute using where clause in ruby - ruby-on-rails

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.

Related

If condition and Short circuit evaluation in rails - DRY

I m doing an app and I need to add multiple condition if. I want to know if an solution exist to DRY.
I m tried something like this but that dont work, it's like https://en.m.wikipedia.org/wiki/Short-circuit_evaluation
<% #camping.situations.each do |situation| %>
<%if situation.plage == "oui" || situation.etang == "oui" || situation.lac == "oui" %>
Do that
<%else%>
Do this
<%end%>
<%end%>
For my app to test more than 30 conditions so i dont want to repeat myself with multiple IF.
Example : Plage = oui / etang = Non / lac = oui
I want to display in my view :
Do that
Do this
Do that
Do you have any suggestions ?
EDIT
I m sorry i think my message wasn't clear. So for each entries I have 3 possible value 1) OUI 2) NON 3) EMPTY. User add the value of his object.
So in my view i display like this
<% #camping.caracteristiquetests.each do |caracteristiquetest| %>
<%if caracteristiquetest.animaux =="oui"%>animaux<%else%><s>animaux</s><%end%>
<%if caracteristiquetest.barbecue=="oui"%>barbecue<%else%><s>barbecue</s><%end%>
<%if caracteristiquetest.handicap =="oui"%>handicap<%else%><s>handicap</s><%end%>
<%if caracteristiquetest.piscine=="oui"%>piscine<%else%><s>piscine</s><%end%>
<%if caracteristiquetest.jeux =="oui"%>jeux<%else%><s>jeux</s><%end%>
<%if caracteristiquetest.loisir=="oui"%>loisir<%else%><s>loisir</s><%end%>
<%end%>
<!--Affiche les parametres de la table situation-->
<% #camping.situations.each do |situation| %>
<%if situation.plage =="oui"%>plage<%else%><s>plage</s><%end%>
<%if situation.etang =="oui"%>etang<%else%><s>etang</s><%end%>
<%if situation.montagne =="oui"%>montagne<%else%><s>montagne</s><%end%>
<%if situation.riviere =="oui"%>riviere<%else%><s>riviere</s><%end%>
<%if situation.foret=="oui"%>foret<%else%><s>foret</s><%end%>
<%end%>
If value is different to "oui" my view show Strikethrough.
I have more entries but it's always the same condition. As you can see i have lot of "IF". So i just wanna know if it's possible to DRY this ?
You could do it as follows.
The code situation.members.any?{|m| situation.send(m) == 'oui'} enumerates the members (attributes) of your situation object and compares the value of them each by doing a send of the membername against the string 'oui', if any of them contains it the whole expression is true.
The ternary statement ? evaluates this and puts the "Do that" if true and the "Do this" else.
situation = Struct.new(:plage, :etang, :lac)
situations = []
situations << situation.new('non','oui','non')
situations << situation.new('non','non','non')
situations.each do |situation|
puts situation.members.any?{|m| situation.send(m) == 'oui'} ? "Do that" : "Do this"
end
# gives
# Do that
# Do this
If I understand your last edit correct, this would become
situations.each do |situation|
situation.members.each{|member| puts situation.send(member) == 'oui' ? "Do that" : "Do this"}
end
You really should make your question more clear next time.
Based on your last edit, my last attempt, you just need to go one level deeper.
We just give you the methods how to do something, the rest is up to you.
More Dry than this it won't become..
camping = Struct.new(:situation, :caracteristiquetest)
campings = []
situation = Struct.new(:plage, :etang, :lac, :montagne, :riviere, :foret)
caracteristiquetest = Struct.new(:animaux, :barbecue, :handicap, :piscine, :jeux, :loisir)
campings <<
camping.new(situation.new('non','oui','non','non','oui','non'),
caracteristiquetest.new('non','oui','non','non','oui','non'))
campings <<
camping.new(situation.new('oui','oui','non','non','oui','non'),
caracteristiquetest.new('non','oui','non','non','non','non'))
campings.each do |camping|
camping.members.each do |camping_attr|
camping.send(camping_attr).members.each{|member|
puts camping.send(camping_attr).send(member) == 'oui' ? member : "<s>#{member}</s>"
}
end
end
Which gives
<s>plage</s>
etang
<s>lac</s>
<s>montagne</s>
riviere
<s>foret</s>
<s>animaux</s>
barbecue
<s>handicap</s>
<s>piscine</s>
jeux
<s>loisir</s>
plage
etang
<s>lac</s>
<s>montagne</s>
riviere
<s>foret</s>
<s>animaux</s>
barbecue
<s>handicap</s>
<s>piscine</s>
<s>jeux</s>
<s>loisir</s>

How to get Rails 4 if a record has two conditions return true else false?

I'm setting up my vote system, and trying to have a helper model so I can check if a user has voted for a card. I'm new to rails and can't seem to figure this one out.
How do I have the model check votes for a record that has the user_id of the current_user and the card_id?
I'm also trying to limit calling the helper many times for each iteration of _cards.html.erb by setting the voted variable. Not sure how to do this, trying to set the variable is just printing true for every card, even the ones that have no votes.
Setting the variable is not working and neither is the helper, as it is always true.
cards_controller.rb:
def if_voted(card_id)
if Vote.where(:user_id => current_user.id, :card_id => card_id) then
true
else
false
end
end
helper_method :if_voted
_cards.html.erb:
<td>
<%= #voted = if_voted(card.id) %>
<% if #voted == true %>
<span class="green"><center>
<% elsif #voted == false %>
<span class="red"><center>
<% else %>
<span class="gray"><center>
<% end %>
<%= card.up_votes - card.down_votes %>
</center></span>
</td>
With the help of #tadman
cards_controller.rb
def if_voted(card_id)
if Vote.where(:user_id => current_user.id, :card_id => card_id).any? then
#vote = Vote.find_by(:user_id => current_user.id, :card_id => card_id)
return #vote.voted
else
return nil
end
end
helper_method :if_voted
_cards.html.erb
<td>
<% #voted = if_voted(card.id) %>
<% if #voted == true %>
<span class="green"><center>
<% elsif #voted == false %>
<span class="red"><center>
<% else %>
<span class="gray"><center>
<% end %>
<%= card.up_votes - card.down_votes %>
</center></span>
</td>
Thank you
The where method always returns a scope even if that scope does not contain any records. The find_by method uses the same options but returns either the first matching record or nil if none are found.
That's not quite what you want here, though. You don't actually want to retrieve any of the records, but instead just check if they exist. The any? method on a scope is true if one or more records exist, or false otherwise.
You should update your code to look like this:
def if_voted(card_id)
Vote.where(:user_id => current_user.id, :card_id => card_id).any?
end
It's worth noting a few things about your Ruby style:
Using then at the end of an if clause, while supported, is extraneous and generally not done.
Comparing things == true is usually a sign your logic is confused. If you're concerned about something being literal true rather than just logically true, use === true instead. In this case, close enough counts, so if (if_voted(...)) should suffice.
Your method returned either true or false but you had three conditions as if you were expecting a maybe to pop up one day.
Method names like if_voted are a little clumsy, especially if used inside an if. Something like has_voted? is much more in line with Ruby and Rails in general, so you get if (has_voted?(...)) which reads a lot better.
Even better would be to migrate this method into the User class so you can eliminate the helper and end up with if (current_user.has_voted?(card_id)) as a very clear way of expressing your intent.

Is this validation almost correct?

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

Ruby on Rails: Sorting collection of objects

I have a method that will provide an array of model object. Some of those model's attributes are easy to sort by using some help from SQL. I mean, using .find(:condition => {})
The problem is some of the other attributes isn't. It has to be calculated, modified, or did something else before showing. Then, I found the way to sort it using collection sorting. Which is, collection.sort{|a,b| a.something_to_sort <=> b.something_to_sort}
OK! That's works. But the problem is, is it possible to make that something_to_sort part to become a dynamic variable? For example, I want to take a parameter from the UI and assign it to that something_to_sort like following,
HTML
<select name="sort">
<option value="name">Name</option>
<option value="age">Age</option>
<option value="activity.category">Activity</option>
<option value="tax.calculate_personal_income_tax">Income Tax</option>
<option value="tax.calculate_withholding_tax">Withholding Tax</option>
<option value="doctor.name">Doctor's Name</option>
</select>
Rails
params[:sort] ||= "Age"
#people = Person.find(:all)
#people.sort{|a,b| a.params[:sort] <=> b.params[:sort]} #Note that this is an incorrect syntax
Is there any way to do this by not having to write a sort block for each of sorting option?
Additional #1
I've just tried this and it was working for some sorting option
#people.sort{|a,b| a.send(params[:sort]) <=> b.send(params[:sort])}
This works for name and age as they are people's attribute (and it works for peopls's method too). But for the association's such as tax.calculate_personal_income_tax, it's not.
So, my colleague told me to create new people's method, calculate_personal_income_tax and then change the option value from tax.calculate_personal_income_tax to calculate_personal_income_tax so that the send method will be able to find this method ...
def calculate_personal_income_tax
tax.calculate_personal_income_tax
end
But, this is not really what I want. Because I believe that there must be a way to access this association method without having to define a new model method. If I choose to do this and one day people model become larger, more attribute, more information to sort and display. Then, there will be a lot of method like this.
The other reason is, if I have to define a new method, that method should have to do some logic, calculation or else, not just retrieve a data from other model.
Additional #2
Finally, I've just found the way to access other model attribute by adding args to the find method, :join and :select.
#people = Person.find(:all, :select => "persons.*, tax.tax_rate AS tax_rate", :joins => :tax, :condition => {some conditions})
The result of that will be a table of person joining with tax. Then, I use the send(params[:sort]) thing to help me sorting the attribute that I want.
#people.sort {|a, b| a.send(params[:sort]) <=> b.send(params[:sort])}
params[:sort] ||= "age"
method_chain = params[:sort].split(".")
#people = Person.find(:all)
#sorted_people =
#people.sort_by do |person|
method_chain.inject(person) do |memo,method|
memo.send(method)
end
end
This assumes that the values passed in params[:sort] are always valid method names which can be chained together in the order specified. You'd want to keep in mind that anything can be passed in the params hash, so this would be very unsafe if exposed to untrusted users (e.g. params[:sort] = "destroy". Oops.)
Also, if you can do this in SQL instead you'll get better performance.
In fact, the more I look at this, the more it seems like a bad idea.
I have a loot system that is doing this on a calculated value, that is based on a reference table which can change. As the value is calculated, it could come from anywhere.
My players/index.html.rb looks something like.
<h1>Players</h1>
<table>
<tr>
<th>Name</th>
<%= generate_headings(params) %>
</tr>
<% players.each do |player| %>
<tr>
<td><%= player.name %></td>
<% LootType.all.each do |loot_type| %>
<td><%= player.loot_rate(loot_type.name) %></td>
<% end %>
<tr>
<% end %>
</table>
The loot_rate function is calculating on two related tables (player.raids and player.items) as "player raids / loot of type + 1".
The generate_headings function is in players_helper.rb, and it looks like:
def generate_headings(params = {})
sort = params[:sort]
headings = ""
LootType.all.each do |loot_type|
heading_text = "#{loot_type.name} Rate"
if (sort == loot_type.name)
column_heading = "<th>#{heading_text}</th>"
else
heading_url = "/players?sort=#{loot_type.name}"
column_heading = "<th>#{link_to heading_text, heading_url}</th>"
end
headings += column_heading
end
headings.html_safe
end
In my index action of players_controller.rb, I have:
def index
sort = params[:sort]
if !sort
#players.sort! { |a,b| a.name <=> b.name }
else
#players.sort! { |a,b| b.loot_rate(sort) <=> a.loot_rate(sort) } # Reverse sort (for my purposes)
end
respond_to do |format|
format.html
format.json {render :json => #players }
end
end
This means that when I add or remove "loot types" in my system, and the player list automagically allows users to sort by those types.
Long winded answer, but hope it helps.

Conditional statement in Rails

I'm trying to get some code to display based on a condition. I have a boolean field in a table called "show_weekly". What I'm attempting is: If the column == 1 then display the first line else display the 2nd line of code. for some reason it's only showing the 2nd line.
<% if #listing.show_weekly == 1 %>
<%= number_to_currency(#listing.price/4, :unit => "£") %> / week
<% else %>
<%= number_to_currency(#listing.price, :unit => "£") %> / month
<% end %>
any help is greatly appreciated. thanks
The value of a boolean column will either be false or true in ruby, not 0 or 1. So you should do:
<% if #listing.show_weekly %>
instead of <% if #listing.show_weekly == 1 %>
I'd suggest adding a method to your model
def print_price
#p = this.show_weekly? ? this.price / 4 : this.price
number_to_currency (#p, :unit => "£")
end
you need to check if it equals true, not 1
A boolean value is stored as a 1 or a 0, but it is always interpreted as true or false before being returned from the model. This is a source of a lot of confusion.
Using the accessor method show_weekly? can help because a question-mark in a method name usually indicates it will return a boolean value, or something that can be evaluated as boolean.
It will be a lot more readable if you have:
<% if #listing.show_weekly? %>
...
<% else %>
...
<% endif %>
Wherever possible, avoid comparing to specific hard-coded values. For instance, the following is redundant and yet I see it all the time:
# Example: Comparison to nil
if (#something == nil)
# Should be: Checking if initialized
if (#something)
# Example: Comparison to true
if (#something == true)
# Should be: Testing directly
if (#something)
As there are only two values that evaluate as false in Ruby, nil and false, it is generally the case that anything that is not true is nil. Occasionally you will have boolean columns that can be true, false or nil if it is not defined, but this is unusual and can trip up people.
If #listing.show_weekly contains a boolean value, just test for true :
<% if #listing.show_weekly %>
.....
<% else %>
....
<% end %>
Notice you don't even need the "== true". This is because the IF statement only looks to see if there's a true value returned from whatever expression follows it. If #listing.show_weekly contains a value of true then that's all the IF statement sees if that's all you provide it.

Resources