Active Record Query - Rails - ruby-on-rails

Currently I have a method called Visit and it basically adds on the the visit counter on Subscriber. This method gets triggered when a Subscriber enters their phone_number in a form. I simply want to make a active record query that will look something like this Susbcriber.find_by(params[id]).last.visit(this doesn't work BTW). Hopefully you get what I'm trying to do there, basically call the person that checked in last with their phone number. I'll show my code for clarity.
CONTROLLER METHOD:
def search
#subscriber = Subscriber.new
end
def visit
#subscriber = Subscriber.find_by(params[:phone_number])
if #subscriber
#subscriber.visit ||= 0
#subscriber.visit += 1
#subscriber.save
flash[:notice] = flash[:notice] = "Thank You #{#subscriber.first_name}. You have #{#subscriber.days_till_expired} until renewal"
redirect_to subscribers_search_path(:subscriber)
else
render "search"
end
end
There you can see the method that gets triggered when a Subscriber inputs their phone number. I simply want to call that person in the console.
CONTROLLER METHOD THAT IS NOT WORKING CURRENTLY:
def create
#subscriber = Subscriber.find(params[:subscriber_id])
#comment = #subscriber.comments.build(comments_params)
if #comment.save
flash[:notice] = "Thank you!"
redirect_to subscribers_search_path(:comments)
else
render "new"
end
end
This is the method that needs the new query to find the Subscriber that just entered their phone number.
Let me know if you need more info? Thank You!

I'm editing my answer to be more inclusive based on the comments. It seems like you've got a number of issues going on here and maybe you need to step back and rebuild this step by step. Consider test driving it and/or at least verify that you're getting what you expect using a debugging tool as you go along (byebug, pry, or even just judicious use of 'puts' and 'inspect').
The find_by method requires that you specify the attribute that you're trying to use to find the row by, as well as the value of the attribute
#subscriber = Subscriber.find_by(phone_number: params[:phone_number])
If you are using the model's primary key, just use find:
#subscriber = Subscriber.find(params[:id])
If you're calling a controller action, params should always be present. Try inspecting the params before moving on with any of the rest of the code. Make sure that you're getting what you expect. If not, evaluate your view code.

Related

Best way to save attribute inside save block?

In my controllers I often have functionality like this:
#account = Account.new(account_params)
if #account.save
if #account.guest?
...
else
AccountMailer.activation(#account).deliver_later
#account.update_column(:activation_sent_at, Time.zone.now)
flash[:success] = "We've sent you an email."
redirect_to root_path
end
end
What's the best way to send an email and update the activation_sent_at attribute without having to save the record twice? Calling update_column doesn't feel right to me here because AFAIK it creates an extra SQL query (correct me if I'm wrong).
Your code is fine. I wouldn't change it.
For example, you might be tempted to do something like this:
#account = Account.new(account_params)
#account.activation_sent_at = Time.zone.now unless #account.guest?
if #account.save
if #account.guest?
...
else
AccountMailer.activation(#account).deliver_later
flash[:success] = "We've sent you an email."
redirect_to root_path
end
end
Aside from the small issue that there's now repeated logic around #account.guest?, what happens if AccountMailer.activation(#account).deliver_later fails? (When I say "fails", I mean - for example - AccountMailer has been renamed, so the controller returns a 500 error.)
In that case, you'd end up with a bunch of account records which have an activation_sent_at but were never sent an email; and you'd have no easy way to distinguish them.
Therefore, this code warrants running two database calls anyway: One to create the record, and then another to confirm that an email was sent. If you refactor the code to only perform a single database call, then you'll become vulnerable to either:
Sending an email to a non-created user, or
Marking a user with activation_sent_at despite o email being sent.
The controller should be doing two transactions, not one. Which is why I said: Don't change it.

How to define a current_model for a model in rails?

I think my question title is bit confusing. But what I am meaning to ask is I am creating my own authentication system using mobile. Just like devise comes with current_user to create a session, I want to know how can I achieve same on a different model.
I have a model called Commuter. It also has a id with it.
A record of commuter looks like this.
Commuter.last
<Commuter id: 867, phone_number: "9483942090">
I am trying to create a session after verfying the mobile number with my controller method as follows:
def verify
#commuter = Commuter.where(phone_number: params[:phone_number]).first
if (#commuter && #commuter.authenticate_otp(params[:otp],drift:300))
#commuter.auth_active = true
if #commuter.save
#Removed from session after verified it
session[:phone_number] = nil
session[:is_verified] = nil
#signed in commuter after verified it
sign_in(:commuter, #commuter)
flash[:notice] = "Your mobile no is verified."
end
else
flash[:alert] = "You have entered wrong otp.Please check again."
end
puts "#{current_commuter.phone_number}"
redirect_to root_path
end
I just a puts there to debug. So right now I am getting current_commuter as undefined local variable for obvious reasons I guess. So I wanted to know how can achieve this session based current commuter ?
You can save the Commuter id in the session as session[:cid] = 1 and create a method on your base controller like this
def current_commuter
#commuter ||= Commuter.find session[:cid]
end
helper_method :current_commuter

How to display errors in activeadmin forms after overriding controller

I have a situation where I need to override create in activeadmin. I autofill the fields and if the data already exists it should update otherwise create. Here is my create method:
def create
id = params[:company].dig(:id)
if id.present?
#company = Company.find(id)
if #company.update(permitted_params[:company])
redirect_to resource_url
flash[:notice] = 'Company created successfully'
else
#add errors to semantic errors
end
else
new_permitted_params = permitted_params[:company].except(:id)
#company = Company.new(new_permitted_params)
#company.save
if #company.errors.any?
#add this to semantic errors so that activeadmin handles and displays the errors
end
end
end
I want to display the errors which violate the validations so that the user knows if he/she has entered an invalid entry.
I found this but it looks like a workaround more than a solution. Please help me solve this.
Thanks in advance.
I created a new HTML file in views named new.html.arb and added insert_tag renderer_for(:new) in it. After that all I did was
if #company.errors.any?
render 'new'
end
I discovered it by seeing the default behavior of activeadmin. I hope this helps other people who are looking to do something similar. This is the result that I get and which was required by me.
I wonder if client side logic to submit to different URLs depending on id.present? might make things more restful.

Is there a way to add to an existing balance or amount in a database column without just replacing it when updating?

I am building a budgeting app of sorts. So I need to be able to make a deposit and a withdrawal. However the update method replaces the previous amount rather than adding to it. When I google this question I get active record migrations. Is there something similar to update_attributes that adds on to rather than replacing? Or is there a db column type that works this way?
This is what I have right now.
def deposit
account = find_account(params[:id])
new_balance = account.balance += params[:account][:balance].to_f
account.assign_attributes(balance: new_balance)
if account.save
redirect_to accounts_path
else
redirect_to :back
end
end
You can use += on a field/attribute, you don't need the assign_attributes
def deposit
account = find_account(params[:id])
account.balance += params[:account][:balance].to_f
if account.save
redirect_to accounts_path
else
redirect_to :back
end
end
Why not pull the existing value to your client code, do your magic adding and then update the record with the new value?

Persisting ActiveRecord objects between requests

I have an ActiveRecord model named Document and have implemented CRUD operations around it. I just have a problem with persisting a Document instance between requests when validation fails (be cause I wanna redirect to another page when this happens).
First, I tried storing the instance in the flash session:
# documents_controller.rb
def new
#document = flash[:document] || Document.new
end
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
With the code above, I was expecting that the actual Document instance was stored in the flash session, but instead it became a string which looks somewhat like #<Document:0xad32368>. After searching online for a while, I found out that for some reasons you cannot store ActiveRecord objects in sessions.
There are a lot of suggestions about just storing the object's id in the flash session, but I can't do that because as you can see, the object is not yet stored in the database.
Next, I tried reconstructing the Document instance after the redirect, taking advantage of the instance's attributes method (which returns a serializeable hash that can be stored in the session):
# documents_controller.rb
def new
#document = Document.new(flash[:document_hash] || {})
end
def create
...
flash[:document_attributes] = document.attributes
redirect_to new_document_path
end
This almost solved the problem, except for the part in which the validation errors (document.errors) are not preserved. Also, if this is used to persist an instance already stored in the database (in the case of failed validations when updating a Document instance), I'm not sure which between the original attributes and the new attributes will get persisted.
Right now I've already run out ideas to try. Anyone who has a decent solution for this?
EDIT:
You might be wondering why I still have to redirect to another page instead of just rendering the new document view template or the new action in the create method. I did so because there are some things in my views that are dependent on the current controller method. For example, I have a tab which needs to be highlighted when you are on the document creation page (done by checking if action_name == "new" and controller_name == "documents"). If I do:
def create
...
render action: "new"
end
the tab will not get highlighted because action_name will now be create. I also can't just add additional condition to highlight the tab if action_name == "create" because documents can also be created from the the index page (documents_path). Documents can also be updated from the index page (documents_path) or from the detail page (document_path(document)), and if validation fails in the update method, I'd like to redirect to the previous page.
If I really need to fake persisting something between requests (all of the variables that you set are lost between requests), I will ususally put the relevant attributes into hidden fields in the new form.
In your case, this is overkill. In your code, you are redirecting, which causes a new request:
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
You can easily render the output of another action, instead of redirecting, by using render action: 'action_to_render'. So in your example, this would probably be:
def create
#document = Document.new(document_params)
if #document.save
render action: 'index'
else
render action: 'new'
end
end
Which can be simplified to:
def create
#document = Document.new(document_params)
action_to_render = #document.save ? 'index' : 'new'
render action_to_render
end
If you need extra logic from the action, you can refactor the logic to a method called from both actions, or simply call the other action from the current one.
It is fine once in a while, but I would caution that having to jerk around with the rendering too much is usually indicative of poor architecture.
Edit:
An additional option, given the newly highlighted constraints, could be to make the new and create methods the same. Remove the new action and routes, and make create answer for GET and PATCH requests. The action might look something like:
def create
#document = Document.new(document_params)
request.patch? && #document.save && redirect_to( documents_path )
end
I actually use something very similar to this for almost all of my controllers, as it tends to DRY things significantly (as you can remove the extra probably identical view, as well)
Another option would be to just use an instance variable to keep track of the active tab in this instance, and make the rest of the code a lot cleaner.
SOLVED
I was able to make a workaround for it using ActiveSupport::Cache::Store (as suggested by #AntiFun). First I created a fake_flash method which acts closely like the flash sessions except that it uses the cache to store the data, and it looks like this:
def fake_flash(key, value)
if value
Rails.cache.write key, value
else
object = Rails.cache.read key
Rails.cache.delete key
object
end
end
And then I just used it like the flash session.
# documents_controller.rb
def new
...
#document = fake_flash[:document] || Document.new
...
end
def create
document = Document.new document_params
...
# if validation fails
fake_flash :document, document
redirect_to new_document_page
end

Resources