decrement a value in view but not database - ruby-on-rails

I have a total value that I would like to keep but I would also like to decrement it every time a certain kind of user creates the resource. For example I have-
if #user == "Premium"
#resource.decrement!(:total)
end
But I would also like to show the first :total that I had. So in other words I would like the original value and the decremented value to show on the view page. like so-
<%= resource.total %>
<%= resource.decremented_value %>
how could I do that? Would I need to create another column in my database?

I'd recommend creating a separate column and then using a new method in your model to decrement the value. For example:
in models/resource.rb
before_create :decrement
def decrement
self.decremented_value = self.total - 1
end
This will only call the decrement method when you create a Resource for the first time. If you edit an existing Resource's total however, it won't decrement the total as it's only listening for create actions. I would suggest looking at this answer for more on listening to updates in the model: https://stackoverflow.com/a/1586641/810794
One last thing: the naming of your deceremented_value and total columns is a little confusing. You may want to change them to current_total and previous_total as these seem to better describe what you're trying to achieve.

Thank you #lukad03,
I did some messing around with your answer and figured out another way myself. I created another column to mimic the first and just made it equal to itself in the resource model using a variable. (like #lukad03 said)
resource.rb
before_create :decrement
def decrement
self.total = self.decrement
end
It is a little bit of a hack but it works just fine.

Related

Ruby Review class creation

The Review class you see below represent a review that a user submitted for a product. Somewhere else in the code, Review.recent is called with a product_id, which is just a unique number that represents a single product. Fill in the code to make it work as expected!
Review.recent - This function should return the 5 most recent reviews (sorted by submit_time) with the specified product_id.
<=> - This special Ruby function is called when comparing two objects for sorting. It returns 1, 0, or +1 depending on whether the object is less than, equal to, or greater than the other object. You'll want to sort items by submit_time so recent items appear first.
set_submit_time - This function is called right before a review is created. We can use Ruby's Time class to set submit_time to now so we know when the review was created.
I'm new to ruby and I want this code for my very important work so how can I complete it help me please!
class Review < ApplicationRecord
# Every Review has a product_id and submit time
attr_accessor :product_id
attr_accessor :submit_time
# Before the new record is created, we'll call the :set_submit_time method
before_create :set_submit_time
def self.recent(product_id)
# Return only the 5 newest results for this product
# Reference: https://ruby-doc.org/core-2.4.2/Enumerable.html
Review.all
end
def <=>(other_review)
# Implement the comparison function for sorting
# Reference: http://ruby-doc.org/core-2.4.2/Comparable.html
end
private
def set_submit_time
# Set the submit_time
# Reference: https://ruby-doc.org/core-2.2.4/Time.html
end
end
self.recent
This is asking you to order by submit_time and return the first 5 results.
To perform the ordering, see: https://apidock.com/rails/ActiveRecord/QueryMethods/order
To perform the limit, see: https://apidock.com/rails/ActiveRecord/QueryMethods/limit
If you're still stuck on this problem, please show us what you've tried.
<=>
If you click the link in the comment you provided (http://ruby-doc.org/core-2.4.2/Comparable.html), the solution is almost identical to that example.
If you're still stuck on this problem, please show us what you've tried.
set_submit_time
It's worth having a quick look at: https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html - to understand what is meant by a callback. Basically, this method is going to get automatically called whenever a new record is created. (You probably could have guessed this, based on the fairly self-explanatory name: before_create!)
Again, the first example on that page is almost identical to your scenario. You can use Time.now to get the current time.
If you're still stuck on this problem, please show us what you've tried.

rails variables in production environment

I am developing a rails app. Most of the parts work fine, but I got one weird problem when I tried to calculate the time an user used to edit and submit one form.
I thought it would be good to do it in the following order:
1. in the controller "edit" method, record the time the user start to see the form.
2. in the "update" method, record the submit time, then do the math and get how long the user had spent on the form.
class Test
##start_time = 0
##end_time = 0
def edit
##start_time = Time.now
end
def update
##end_time = Time.now
time_used = ##end_time - ##start_time
puts time_used.to_i
end
end
The code above actually works fine while running on my own developing computer, the output is what I expected. But when I upload the code to the production environment(multicore cpus), sometime the output is right, sometime it is not. I debugged the code and found in some case, the ##start_time is set to 0 when submitting the form. I am confused what was going on, maybe I just misused the ## for the variable. Please help me out, any idea would be appreciated, thanks.
Edit:
The problem is solved by adding a virtual attribute to the model as hinted by Vishal. In addition, I added a hidden field in the submit form, and in the strong parameter part added the corresponding parameter to allow it to be passed from edit to update method.
Your solution will create conflicts when more than two users try to edit simultaneously, So basically what idea I have is:
Add one virtual attribute in your model edit_start_time You don't need attribute for endtime because it can be directly fetched by Time.now at any time.
Set edit_start_time value in edit method like:
#model.edit_start_time = Time.now.utc #you can use any
In update method directly calculate edit time like:
total_update_time = Time.now.utc - #model.edit_start_time.utc
If you are unaware of how to create virtual attributes then there are so many questions on StackOverflow as well as docs. I am not explaining how to do it here because its the different topic.
All the best
You're using class variables that can interfer with each other. Your Test class will only ever have one class variable called ##start_time associated with it.
This means if another user sees the form, they will reset the ##start_time for every user currently on it.
To prevent this, use instance varaibles. When a new user sees the form, they will make a new instance variable that is tied to their instance of the class, rather than the class itself. This will allow users to have different start and end times.0
All you need to do is change every ## to #. So instead of ##start_time', try#start_time` throughout your code, and the same for end_time.

How to set parent attribute based on child attributes

I have a situation where a parent Order has multiple child Items. Both Order and Item have a status_id column. I want the user to update the status_id of the Item, and then when all of the Items have a status_id, then the Order's status_id should be auto-set (to some value based on what Item status_ids are).
The code that I currently have is this:
class Item
after_save :set_order_status_id
def set_order_status_id
if self.order.items.where(status_id:nil).blank?
self.order.update_attributes(status_id:X)
end
end
end
Ths is pretty smelly code because it violates SRP, uses a callback, and is pretty inefficient, considering that this means if an Order has 5 Items and all 5 Items are being updated, after EACH Item update, the set_order_status_id method is called, and a database query is run.
So... is there a better way of writing this to avoid these issues? Particularly I'm interested in removing the inefficiency with constantly checking the parent Order's other child Items' statuses... because again if an all 5 Items is updated at once, it's silly to check after each and every update when it should just wait until the 5th update.... Does Rails have a magical way of doing this?
The answer is no, there is no magic way to do that using the framework. Your best option is to use a customised solution and run your check only after your items update. Something like:
...your code...
order.items.update_all(whatever) <-- update items
update_status(order) <-- update order status
...
def update_status(order)
return if order.items.where(status_id: nil).exist? <-- more efficient
update_attributes(status_id: X)
end
the method could also be in the Offer model for simplicity.
If the order status can be derived from the item status at any point in time, it may be better to avoid setting it in the database entirely. You can instead create an accessor to query it on-demand:
class Order < ActiveRecord::Base
def status
# memoize the calculation, including nils
return #status if defined? #status
item_statuses = items.pluck(:status).uniq
# your logic here:
# 1. check for any nils
# 2. check for any 'pending', etc.
#status = 'pending'
end
end
Whether this alternate solution fits your needs depends on your database read patterns.

Creating Rails todo list app with goals and tasks on one page - missing params

I'm a Rails beginner, and have been reading tutorials and typing out applications for a few months now. I'm really enjoying it after a few years spent in the front end world, and beginning to get up to speed with it all. The time has come though for me to start building my own stuff without any handholding. So far, so good.
I'm creating a basic to-do list app, where goals and tasks are displayed on the same page - goals#index. My issue is that I'm not sure how to get all tasks for a particular goal (that belongs to a user). I understand that I need to pass an ID param to the Goal model in order to find out its tasks, like so:
Goal.find(1).tasks
The above works fine, as I've already set up foreign keys on the tasks table and have a has_many :tasks relationship for the Goal model and a belongs_to relationship for the Task model.
Here's my Goals controller:
def index
#user = current_user
#goals = #user.goals # list all goals for the current user and assign it to the #goals variable.
# Need to find all tasks for each goal ID and assign it to the #tasks variable. Goal ID needs to be supplied here, but it isn't as we're not in show action.
#tasks = Goal.find(1).tasks
As I said, I can find all tasks for a Goal when I manually enter the ID (1 in this example). This works fine in my app, no errors. But obviously I want to supply these IDs dynamically, and I'm just not sure how I get the params in there.
I have tried the below:
#tasks = Goal.find(params[:id]).tasks
and
#tasks = Goal.find(params[:goal_id]).tasks
And I get the "Couldn't find Goal without an ID" error when I try to iterate over #tasks in my view. Which makes sense, as I don't think the goal params are being passed to it as we're not in the Show action.
Surely there must be an easy Rails way?! I'm stumped and don't really know where to look. Thanks for your help and Happy New Year.
You are getting current user's goals, so when you will do this you have one array object. so when you will pass array object to find, it will have multiple IDS. so when need to find All the tasks from all goals you just need to pass Array of IDS instead of single value.
#tasks = Task.where(:goal_id => #goals)
This will run this SQL query.
SELECT "tasks".* FROM "tasks" WHERE "tasks"."goal_id" IN (SELECT
"goals"."id" FROM "goals")
So when you are dealing with array just pass ids. for e.g. [1,2,3]
Once you do #goals = #user.goals (assuming that's working, which it sounds like it is), you have your goals and there is no reason to go back to the DB to "find" them.
To get ALL your tasks from ALL of user's goals, you can do the following:
#tasks = []
#goals.each do |goal|
#tasks << goal.tasks
end
Ah of course, #goals is an array of the user's goals so I can just work with that. So simple when someone just tells you. Thanks for all your help!
Here's my final code that works (I left the controller unchanged). This gets the first goal in the array and then gets the tasks associated with each goal. I have a set number of goals so I can just use goals[0], goals[1] or goals[2] for each goal.
<% #goals[0].tasks.each do |task| %>
<li><div class="task-item"><%= task.task_name %></div></li>
<% end %>

Controllers in Rails do not "care" how requests are executed

I was reading an article about Rails controllers, can you help me understand please what is meant by the following phrase:
"The best controller is Dilbert-esque: It gives orders without knowing (or caring) how it gets done."
Is it true, in your opinion?
If, for example, I am accessing the index page associated with the subjects controllers, I would define the index method in the subjects_controller.rb rigorously, so I am confused as to what they mean in the article, as I would have thought the opposite.
Any pointers, please?
Thank you and sorry if this is too interpretable. This is the original article: http://betterexplained.com/articles/intermediate-rails-understanding-models-views-and-controllers/
This article is talking about MVC architecture. What's important to take away from an article like this is the fact that Rails is best written with Fat Models and Thin Controllers. This means that you want to have the bulk of your methods/functions in your Model and want to have calls to the functions from your controller. Index is a bad example since typically you're not going to have a lot going on in there.
Your controller for index will typically look something like this
def index
#subjects = Subject.all
end
If you want to scope order for displaying your subjects though, you would do that in your model with a block as follows:
default_scope { order("id DESC") }
A less contrived example might look something like this: Say for example you have an app that accepts input, takes that input and tallies several counters based on what the user entered. Your controller might be named subject_tally and look like this:
def subject_tally
#subject = Subject.find(params[:id])
#subject.winnings += 1
#subject.total_matches += 1
#subject.win_percentage = #subject.winnings.to_f/#subject.total_matches
redirect_to subjects_path
end
THIS IS WRONG. This is a very fat controller and easily moved to the Model where it should be.
If written properly it would look something like this:
subjects_controller.rb: (The Controller)
def subject_tally
#subject = Subject.find(params[:id])
#subject.subject_tally
redirect_to subjects_path
end
subject.rb: (The Model)
def subject_tally
self.winnings += 1
self.total_matches += 1
self.win_percentage =winnings.to_f/total_matches
end
So as you can see, you make only one call from the controller and it "doesn't care" what is actually going on in the backend. It's literally there to pass a value (in this case, the ID of the subject in question) and direct you to another page, in this case, the index.
Furthermore, if you'll notice, you don't need to add that pesky #subject everywhere in your model's subject_tally function... you can reference the attributes of the object just by using self.winnings where you're assigning to an attribute. Ruby is smart enough to know the current subject the method applies to (since you called that function ON a subject from the controller) and in fact you don't even need the self. if you're just retrieving the attributes instead of assigning them... which is why we didn't need self before winnings.to_f or the last line's total_matches.
Very convenient, less code, less time, yay.
The best controller is Dilbert-esque: It gives orders without knowing
(or caring) how it gets done.
means that you should put less logic as you can in the controller,
the controller should only know what to call to get what it needs, and should not know how to carry out a certain action.
In the "Sandy Metz rules" for rails developers (http://robots.thoughtbot.com/sandi-metz-rules-for-developers), she says:
Controllers can instantiate only one object. Therefore, views can only
know about one instance variable and views should only send messages
to that object
only one object could seem a bit extreme, but makes the idea about how much business logic (no logic) you should put in the controller.

Resources