Toggling between status on model with Rails 4 - ruby-on-rails

The scenario
There is a table on my database called budgets. It has author_id, status, answered_at columns.
The goal
When status != 1, then author_id and answered_at should be nil/null.
The problem
I have the following method on my budgets_controller.rb:
def update
budget = Budget.find(params[:id])
budget.update_attributes(status: 1, author_id: current_user.id, answered_at: DateTime.now.to_date)
budget.save!
end
I want to know if is possible to reuse the same method (update) to change the author_id and the answered_at to null and the status itself to 0 if it is already 1. Some kind of toggle.
Knowledge
I saw Rails offers this method to toggling boolean values, but I can't see how it can suit my need since I'm working with two other columns which aren't booleans.

I think what you probably want is a model callback. In your instance, something like
# on models/budget.rb
before_update :nullify_author
def nullify_author
if status == 1
author = nil
answered_at = nil
status = 0
save
end
end
Also, you shouldn't use toggle here. Ruby's falsiness is WAY more restrictive than JavaScript's. In Ruby's case, only false and nil are falsy. Relevantly to you, 0 is not falsy but truthy. To prove it, try !0. The returned value is the boolean false not the FixNum 1

Related

Sort a returned object by its boolean parameters [Ruby]

i think i used the right terminology for what i need, i currently have a database call in my home_controller that is returning a call to my database with all the entries in that table specified, Freelancer.
There is an attribute on these records that has either a true or false value, which is "featured".
I need a way to call a sort method, or some other way, on that object with the true being first and then the false being afterwards, i tried using this code
def index
#freelancers = Freelancer.all
p 'below im outputting featured freelancer i hope'
#freelancers.sort_by { |row| [row.featured ? 0 : 1, row.id]}
p #freelancers
end
But unfortunately this did not work, can anyone advise me on a way to get this to work? Id rather have the sorted object returned as is, rather then assigning it to a new one. Just for future features of adding pagy and a filter by cost.
Use order method
def index
#freelancers = Freelancer.order(featured: :desc)
end

Is there way to call a table as a variable in Active Record?

The title is a bit confusing, so let me explain.
I have 3 Model classes called Table1, Table2, and Table3. All three tables have the "total" column.
This is what I want to be able to do:
index = either 0, 1, or 2
tableNames = ["Table1", "Table2", "Table3"]
tableNames[index].total
^ Obviously I can't do that because tableNames[index] returns a string, not a reference to the actual class itself.
This is what I'm currently doing:
index = either 0, 1, or 2
if index == 0 then
Table1.total
elsif index == 1 then
Table2.total
elsif index == 2 then
Table3.total
end
I guess what I want to do is a bit analogous to the "send" method in ruby, where you can use variables as method names.
Is there a way to do this, or do I have to do the if elsif check? This makes the code longer and clunkier and I'm wondering if there's a better way. Thanks!
If you are getting the model name as a string. You can do this
model_name = "Table1" #or "Table2", "Table3"
model = model_name.constantize
model.total
You can directly turn any string into a class with constantize method.
Note - If you are going to use rails further, ideally refer to them as Models not tables.

Rails scope/class method selecting virtual attribute

I'm currently using the has_scope gem in conjunction with scopes/class methods on models to filter result sets returned to my UI.
I have a model Task which I'd like to be able to filter by status -- a virtual attribute on the model.
The Task model has a few attributes which you might need to know about
uuid: Generated by SecureRandom
running: A boolean
success: A boolean
started_at: DateTime
The status is calculated as such:
def status
if running?
'Running'
elsif started_at.nil?
'Queued'
elsif success?
'Finished'
else
'Failed'
end
end
Ignoring the fact that this is probably not an ideal way of doing this, I have currently implemented the status filtering method like so:
def self.by_status(status)
records = all.select {|s| s.status == status}
where(uuid: records.map(&:uuid))
end
I cannot return just the results from select as it's of type Array as opposed to an ActiveRecord::Relation, hence my where hackery. For context the reason I cannot/do not want to return an array is the result-set is passed to kaminari for pagination.
Please note: The method I currently use meets my requirements, but I don't like the way it's done. I feel like there should be a better way.
Can anyone suggest a better by_status method for returning an ActiveRecord::Relation?
tl;dr:
Need a class method to return an ActiveRecord::Relation filtering on a virtual attribute.
Thanks in advance.
Here is how I would do it
def self.by_status(status)
case status
when 'Running' then where(running: true)
when 'Finished' then where(success: true)
when 'Queued' then where(started_at: nil)
else scoped #change this to all in Rails 4 since Model.all returns an AR Relation
end
end

Better way to write before_save function to check all targeted attributes not blank?

On my project, I have a model with four attibutes: name, desciption, content and completed.
I want to check all attributes except completed whether blank or not before save. If not blank, set completed to 1, otherwise, 0.
I use ActiveRecord and Mysql, so it would have id, created_at and updated_at columns also.
I wrote a before_save callback like this:
def check_completed
if self.attributes.except("id", "created_at", "updated_at").all? {|k, v| v.present?}
self.completed = 1
else
self.completed = 0
end
end
It works, but it looks ugly. I want to remove the except function call.
Is there any better way to do this?
I assume that the .except method was used because id, created_at, and updated_at are all internally generated and managed by MySQL. So, it would be unusual for that list to expand or change. I agree the code is good as provided. If you wanted to shorten it at all, you could use a ternary as in:
def check_completed
self.attributes.except("id", "created_at", "updated_at").all? {|k, v| v.present?} ? 1 : 0
end
Eliminating the .except method exposes you to managing this method any time the model changes.
Extending this discussion:
I am curious about your desire to return 1 or 0? Not seeing more code, I am not sure of your intentions. However, if a "before" callback returns false, execution is stopped and the transaction is rolled back. In any other case, execution continues. In Ruby, 0 is not false. False is only triggered by either false or nil. My expectation would be that it would be more likely to use true in place of 1 and false in place of 0? if so, the code would be:
def check_completed
self.attributes.except("id", "created_at", "updated_at").all? {|k, v| v.present?} ? true : false
end
Such that if any user attribute was not present, the transaction would be canceled and rolled back. but, that is up to you.
I would suggest not relying on self.attributes since you may want to add additional attributes to your model in the future. If so, you'll have to add new attributes to your except list.
Instead you could do something like this:
self.completed = %w(name description content).all? { |attr| !send(attr).blank? } ? 1 : 0

How to update a single attribute without touching the updated_at attribute?

How can I achieve this?
tried to create 2 methods, called
def disable_timestamps
ActiveRecord::Base.record_timestamps = false
end
def enable_timestamps
ActiveRecord::Base.record_timestamps = true
end
and the update method itself:
def increment_pagehit
update_attribute(:pagehit, pagehit+1)
end
turn timestamps on and off using callbacks like:
before_update :disable_timestamps, :only => :increment_pagehit
after_update :enable_timestamps, :only => :increment_pagehit
but it's not updating anything, even the desired attribute (pagehit).
Any advice? I don't want to have to create another table just to count the pagehits.
As an alternative to update_attribute, In Rails 3.1+ you can use update_column.
update_attribute skips validations, but will touch updated_at and execute callbacks.
update_column skips validations, does not touch updated_at, and does not execute callbacks.
Thus, update_column is a great choice if you don't want to affect updated_at and don't need callbacks.
See http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html for more information.
Also note that update_column will update the value of the attribute in the in-memory model and it won't be marked as dirty. For example:
p = Person.new(:name => "Nathan")
p.save
p.update_column(:name, "Andrew")
p.name == "Andrew" # True
p.name_changed? # False
If all you're wanting to do is increment a counter, I'd use the increment_counter method instead:
ModelName.increment_counter :pagehit, id
Is there a way to avoid automatically updating Rails timestamp fields?
Or closer to your question:
http://blog.bigbinary.com/2009/01/21/override-automatic-timestamp-in-activerecord-rails.html
it is not a good idea to do this:
self.class.update_all({ pagehit: pagehit+1 }, { id: id })
it should be
self.class.update_all("pagehit = pagehit + 1", { id: id })
the reason is if two requests are parallel, on the first version both will update the pagehits with the same number, as it uses the number saved in the Ruby memory. The second option uses the sql server to increase the number by 1, in case two of these queries come at the same time, the server will process them one after the other, and will end up with the correct number of pagehits.
To avoid Monkeypatchingtroubles you could also use ModelName.update_all for this purpose:
def increment_pagehit
self.class.update_all({ pagehit: pagehit+1 }, { id: id })
end
This also does not touch the timestamps on the record.
You also have decrement and increment (and their bang versions) which do not alter updated_at, do not go trigger validation callbacks and are obviously handy for counters / integers.
If precision is not really that important, and you don't expect the code to run many times, you can try altering the saved in the database updated_at value, like so:
u = User.first
u.name = "Alex 2" # make some changes...
u.updated_at = u.updated_at + 0.000001.second # alter updated_at
u.save
so that Rails will actually try to save the same value, and not replace it with Time.now.

Resources