Hartl Section 9.1.3: test_unsuccessful_edit fails with error ParameterMissing "user" - railstutorial.org

I'm running through Michael Hartl's Ruby on Rails Tutorial, and have run into a problem I have been unable to troubleshoot. The test for editing users (users_edit_test.rb) fails for reasons I don't understand. Since the tutorial is mostly an exercise in copying code, if have checked and double-checked my typing, but I can't find where I messed up. And, more importantly, I want to understand what is going on. Any help you can provide would be appreciated.
The error message (included below) seems to indicate that the test is launching the edit method in the users controller (users_controller.rb), which in turn launches the private user_params method in the same controller. The user_params method sets strong parameters that require the user parameter, and permit the name, email, password, and password_confirmation parameters. The user parameter does not exist when the test runs, so it returns the error.
My first question is, "What is the user parameter?" The name, email, password, and password_confirmation parameters all refer to columns in the users database, and there is also an id column. But there is no user column. The test and the controller both have an #user variable. Is that the user parameter? That doesn't seem to make sense, as I ought to be able to call the variable whatever I want to, right? I feel like there is something fundamental here that I am not getting, and if I got it, I could troubleshoot this and answer the second question, "What did I screw up/how do I fix this?", myself.
users_edit_test.rb
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
end
test "unsuccessful edit" do
get edit_user_path(#user)
#assert_template 'users/edit'
#patch user_path(#user), user: { name: "",
# email: "foo#invalid",
# password: "foo",
# password_confirmation: "bar" }
#assert_template 'users/edit'
end
end
users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
if #user.update_attributes(user_params)
# Handle a successful update.
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name,
:email,
:password,
:password_confirmation)
end
end
users.yml
michael:
name: Michael Example
email: michael#example.com
password_digest: <%= User.digest('password') %>
Test Output
Started
ERROR["test_unsuccessful_edit", UsersEditTest, 2015-05-14 00:05:19 +0000]
test_unsuccessful_edit#UsersEditTest (1431561919.08s)
ActionController::ParameterMissing:
ActionController::ParameterMissing: param is missing or the value is empty: user
app/controllers/users_controller.rb:34:in user_params
app/controllers/users_controller.rb:24:in edit
test/integration/users_edit_test.rb:10:in block in class:UsersEditTest
app/controllers/users_controller.rb:34:in user_params
app/controllers/users_controller.rb:24:in edit
test/integration/users_edit_test.rb:10:in block in class:UsersEditTest
27/27:
[==================================================================]
100% Time: 00:00:00, Time: 00:00:00
Finished in 0.69446s 27 tests, 60 assertions, 0 failures, 1 errors, 0 skips

For starters: you probably should start your test with a log_in as(#user) before get edit_user_path(#user).
That is part of the question: the logged in user provides the params-info you are looking for.
The params are sent by the browser, to one of the controller actions. This is probably one of the hard parts when you are starting Rails; you may want to find some info on the request/response cycle in rails, and how they are mapped to the controller actions.
(And by the way, why are your test lines commented out in your question?)

You should implement method Update, then there is no errors.
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end

Related

Django - Modelform with override password field : initial value not visible (and not saved) in Based UpdateView

I use a form for handling FTP/SFTP (files) accesses. They can be sometimes updated.
My password field is a Charfield (not required) in my model but I wan't to hide it on updates. I use Bcrypt when saving it.
For this I followed the example here for adding the PasswordInput() widget to my field. I use Crispy so my form in my template is just displayed with {% crispy form %}.
class FlowStockAutoForm(forms.ModelForm):
class Meta:
model = FlowStockAuto
exclude = ['fl_st_auto_report_cross_import', ]
fl_st_auto_pwd = forms.CharField(widget=forms.PasswordInput,
label="Password",
required=False, help_text="BlaBla")
def __init__(self, *args, **kwargs):
super(FlowStockAutoForm, self).__init__(*args, **kwargs)
pwd = self.instance.fl_st_auto_pwd
self.fields['fl_st_auto_pwd'].initial = pwd
In Django Doc, it's recommended to set an initial value with init (as done above) but when the password widget is active, on my update template, the field remains empty. So if I just wanna update another field, when I save, password is saved blank. It's ok when the Password Widget is off.
I would like it to be prefilled but "blurred" and check when saving if its value has changed.
What do I miss ?
Visibly almost duplicated question
had to change from
fl_st_auto_pwd = forms.CharField(widget=forms.PasswordInput,
label="Password",
required=False, help_text="BlaBla")
to
fl_st_auto_pwd = forms.CharField(
widget=forms.PasswordInput(render_value=True))
by adding render_value=True
I also had to remove
label="Password", required=False, help_text="BlaBla"
and replace it with
self.fields['fl_st_auto_pwd'].label = 'Password'
self.fields['fl_st_auto_pwd'].help_text = 'BlaBla'

Trying to catch MsgBox text and press button in xlwings

So I have some code which uses xlwings for writing data in Excel file, xlsm.
after i've done writing, I press a certain button to calculate.
sometimes, an error/message pops in the Excel, which is fine, but i want to catch this message to python, and write it later to a log/print it.
also, i need to interact with this message, in this case to press "Ok" in the message box
Attached image of the message box
So guys, I've been able to solve this with an external python library.
here is the code:
from pywinauto import application as autoWin
app = autoWin.Application()
con = app.connect(title = 'Configuration Error')
msgText = con.Dialog.Static2.texts()[0]
con.Dialog.Button.click()
con.Dialog.Button.click()
print(msgText)
basically, what it does, is connecting to the app, and searching for the title.
in this case "Configuration Error"
it needs to perform double click in order to press "Ok" to close the message.
Secondly, it gets the text from the message, and can forward it wherever i want.
important part to remember though, because this should be an automated task, it should run concurrently, which means Threading.
so, a simple Thread class below:
class ButtonClicker(Thread):
def __init__(self):
Thread.__init__(self)
self._stop_event = Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
def run(self) -> None:
while True:
time.sleep(3)
try:
app = autoWin.Application()
con = app.connect(title='Configuration Error')
msg_data = con.Dialog.Static2.texts()[0]
while True:
con.Dialog.Button.click()
# con.Dialog.Button.click()
# print(msg_data)
return msg_data
except Exception as e:
print('Excel didnt stuck')
break
and of course to actually use it:
event_handle = ButtonClicker()
event_handle.start()
some manipulation is needed in order to work in different codes/scenarios, but at least I hope i will help others in the future, because this seems to be very common question.
#Danny's solution, i.e. pywinauto and Thread, works perfectly in my local machine, but it seems can't catch the message box when Excel is running in server mode, e.g. in my case, the automation is triggered in local and started by a system service installed in the server.
pywinauto.findwindows.ElementNotFoundError:
{'title': '<my-wanted-title>', 'backend': 'win32', 'visible_only': False}
It is finally solved with another python third-party library pywin32, so providing a backup solution here.
'''
Keep finding message box with specified title and clicking button to close it,
until stopped by the main thread.
'''
import time
from threading import Thread, Event
import win32gui
import win32con
class ButtonClicker(Thread):
def __init__(self, title:str, interval:int):
Thread.__init__(self)
self._title = title
self._interval = interval
self._stop_event = Event()
def stop(self):
'''Stop thread.'''
self._stop_event.set()
#property
def stopped(self):
return self._stop_event.is_set()
def run(self):
while not self.stopped:
try:
time.sleep(self._interval)
self._close_msgbox()
except Exception as e:
print(e, flush=True)
def _close_msgbox(self):
# find the top window by title
hwnd = win32gui.FindWindow(None, self._title)
if not hwnd: return
# find child button
h_btn = win32gui.FindWindowEx(hwnd, None,'Button', None)
if not h_btn: return
# show text
text = win32gui.GetWindowText(h_btn)
print(text)
# click button
win32gui.PostMessage(h_btn, win32con.WM_LBUTTONDOWN, None, None)
time.sleep(0.2)
win32gui.PostMessage(h_btn, win32con.WM_LBUTTONUP, None, None)
time.sleep(0.2)
if __name__=='__main__':
t = ButtonClicker('Configuration Error', 3)
t.start()
time.sleep(10)
t.stop()

How can I scope created_at between two times and use said scope on an instance?

I'm trying to create a scope, active, that queries for entries of a class, Shift, that are 5 hours old. I'm then trying to take the scope and apply it to an instance of Shift so I can have the shifts controller redirect to show the active shift. Here's shift.rb:
class Shift < ActiveRecord::Base
scope :active, -> { where(created_at: (Time.now.ago(5.hours)..Time.now)) }
validates :created_at, uniqueness: true
validates_time :created_at, :between => ['9:30pm', '2:30am']
validate :active_shift_limit
private
def active_shift_limit
if Shift.active.count >= 1
errors.add(:base, "Shift already exists for tonight")
end
end
end
First off, I'm having problems with the scope. I created a shift 8 hours ago and am querying for it in the console.
Shift.active.exists?
Shift Exists (0.2ms) SELECT 1 AS one FROM "shifts" WHERE ("shifts"."created_at" BETWEEN '2015-09-14 22:48:35.929658' AND '2015-09-15 03:48:35.929768') LIMIT 1
=> true
So I take a look at the entry.
Shift.active
Shift Load (0.2ms) SELECT "shifts".* FROM "shifts" WHERE ("shifts"."created_at" BETWEEN '2015-09-14 23:03:38.195154' AND '2015-09-15 04:03:38.195191')
=> #<ActiveRecord::Relation [#<Shift id: 32, shift_id: nil, vehicle_id: nil, user_id: nil, created_at: "2015-09-15 00:11:32", updated_at: "2015-09-15 00:11:32">]>
The scope is registering a range into tomorrow, I don't know why, so I try entering the range I've given the scope into the console to see if the problem is there:
Time.now.ago(5.hours)..Time.now
=> 2015-09-14 16:08:35 -0700..2015-09-14 21:08:35 -0700
Alright. At this point I'm stumped. Where am I going wrong?
As for the second part of the question, calling the scope on an instance of Shift, I'm also stumped. I've realized, after some tinkering, that scope is a class method. So I figured calling it on the method and putting that inside an instance variable would work. No cigar. I get this error:
ActiveRecord::RecordNotFound in ShiftsController#create
"Couldn't find Shift without an ID"
Here's my shifts_controller.rb:
class ShiftsController < ApplicationController
skip_before_filter :verify_authenticity_token, :only => :create
def show
#shift = Shift.find(params[:id])
end
def create
#active_shift = Shift.active
if Shift.active.exists?
redirect_to shift_path(#active_shift)
else
#shift = Shift.new
#shift.save
redirect_to shift_path(#shift)
end
end
end
I'm using this gem for validates_time in shift.rb:
gem 'jc-validates_timeliness', '~> 3.1.1'. I heavily referenced this in figuring out the scope: How do I validate one post per day?

Form Gathering Info from Two Other Forms

I have a form that creates a New Work Order. I want to be able to pull the ClientID from the New Client Form or the Main Menu, whichever is open. However I am not getting the desired results:
I have used =IIf(IsNull(Forms![New Client]![txtClientID]), Forms![Main Menu]![txtClientID], Forms![New Client]![txtClientID]) in the Default Value of the Control on the New Work Order Form. I get the correct ID when I go to the form from New Client, but a #Name error when I try to access it from the Main Menu.
What can I do to make it work?
You need to check if the form is loaded, for example (you need to add your own error traps):
Function IsLoaded(ByVal strFormName As String) As Boolean
Const conObjStateClosed = 0
Const conDesignView = 0
If SysCmd(acSysCmdGetObjectState, acForm, strFormName) <> conObjStateClosed Then
If Forms(strFormName).CurrentView <> conDesignView Then
IsLoaded = True
End If
End If
However, it may be easier to use OpenArgs ( http://msdn.microsoft.com/en-us/library/office/ff820845(v=office.15).aspx )
In which case you could say something like:
If IsNull(Me.OpenArgs) Then
MsgBox "No openargs"
Else
Me.txtClientID = Me.Openargs
End If
Or even use Openargs to set the default value.

How to open another Form from current Form?

Our first form is the LOG IN form..how can I open to the next form after logging in?
In your log-in form, I assume you perform your validation inside of the Click event method for a button control. So you would have something like:
Private Sub btnLogin_Click()
If ValidatePassword(txtPassword.Text) Then
' The password is correct, so show the main form and close the login form
MainForm.Show
Unload Me
Else
' The password is incorrect, so leave this form on screen
MsgBox "Invalid password entered!", vbError
txtPassword.SetFocus
End If
End Sub
The two interesting features of this code are:
The Show method, which you call on the form object that you want to show.
In this case, it will probably be your main form—replace MainForm with whatever it is called.
The Unload statement, which closes and destroys the specified form.
In this case, Me refers to the login form since you're finished with it.
You will need call Show on the form which needs to be displayed post login form. You can read more about Understanding Forms and form events
My approach is to avoid trying to open a logon Form as the first Form.
Instead let the main Form be first, and in its Load event Show your logon Form as a modal dialog. This can be done revealing the main Form first by doing a Show on it. Example based on the standard template "Log in Dialog" Form with some code changes:
frmMain.frm
Option Explicit
Private Sub Form_Load()
Dim Control As Control
Show
frmLogin.Show vbModal, Me
With frmLogin
txtSuccess.Text = CStr(.LoginSucceeded)
If .LoginSucceeded Then
'Proceed normally, perhaps after capturing
'the User Name, etc.
txtUserName.Text = .User
txtPassword.Text = .Password
Else
'Do "Unload Me" or disable all controls
'as shown here, etc.
For Each Control In Controls
On Error Resume Next
Control.Enabled = False
On Error GoTo 0
Next
End If
End With
Unload frmLogin
End Sub
frmLogin.frm
Option Explicit
Public LoginSucceeded As Boolean
Public User As String
Public Password As String
Private Sub cmdCancel_Click()
LoginSucceeded = False
Hide
End Sub
Private Sub cmdOK_Click()
'Check for correct password, hard-coded here.
If txtPassword.Text = "password" Then
LoginSucceeded = True
User = txtUserName.Text
Password = txtPassword.Text
Hide
Else
MsgBox "Invalid Password, try again!", , "Login"
With txtPassword
.SetFocus
.SelStart = 0
.SelLength = Len(.Text)
End With
End If
End Sub