I have yet another problem with testing in Elixir and Phoenix. I have a module which represents users which looks like so:
defmodule AppExample.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
alias SecuritytrailsApi.Accounts.User
schema "users" do
field :email, :string
field :password, :string, virtual: true
field :password_hash, :string
field :last_login, :naive_datetime
timestamps()
end
...
def registration_changeset(%User{} = user, attrs \\ %{}) do
user
|> changeset(attrs) # validating and castnig email
|> validate_required([:password])
|> put_password_hash # creates password hash from password
|> set_default_date
end
defp set_default_date(chanegset) do
case changeset do
%Ecto.Changeset{valid?: true} ->
put_change(changeset, :last_login, Ecto.DateTime.utc)
_ -> changeset
end
end
end
And let's say I have an Accounts context which is responsible for creating new accounts. Like so:
defmodule AppExample.Accounts do
def create_user(attrs \\ %{}) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert
end
end
So as you can ese, the field last_login is inserted automatically using Ecto.DateTime.utc to get the now() time. So, my questions are the following:
1) Is this the correct way of adding a default now() value to a field in Ecto?
2) How can I write tests for this? I don't know how can I get the value of that particular field. Please check the example below for details.
defmodule AccountsTest do
#valid_attrs %{email: "mail#mail.com", password: "123123"}
test "create_user/1 creates a new users with last_login set to now" do
assert {:ok, %User{} = user} = Accounts.create_user(#valid_attrs)
assert user.email == "mail#mail.com"
assert user.last_login == ### ????? How can I get that NOW value here for the test
end
end
1) Is this the correct way of adding a default now() value to a field in Ecto?
You can default this field to now() in a migration instead of computing its value manually.
alter table(:users) do
modify :last_login, :string, default: fragment("now()")
end
2) How can I write tests for this?
You could assert that the value is in the last e.g. 15 seconds:
assert DateTime.diff(DateTime.utc_now, user.last_login, :second) <= 15
Related
How does one go about partial updates (i.e. via PATCH)? rake routes indicates that def update handles PUT and PATCH. This is how my Rails API is setup:
#user.first_name = user_params[:attributes][:'first-name']
#user.last_name = user_params[:attributes][:'last-name']
In user model. Both first_name and last_name have validates … presence: true. However, client, is trying to hit the endpoint with just attributes[first-name]. Note, attributes[last-name] is not being passed in the request. Rails thinks that #user.first_name has a value, but #user.last_name is nil. So a validation error is thrown
One way I thought of going about this was something like:
#user.first_name = user_params[:attributes][:'first-name'].present? ? user_params[:attributes][:'first-name'] : #user.first_name
#user.last_name = user_params[:attributes][:'last-name'].present? ? user_params[:attributes][:'last-name'] : #user.last_name
Is this a viable approach? Or is there something better I can consider?
EDIT. A more sophisticated problem is when I need to pre-calculate before actually saving the object. Take for example a product trying to update its price against a discount value, if present
def update
product = Product.find(params[:id])
product.amount_in_cents = product_params[:attributes][:'amount-in-cents']
product.discount_in_percentage = product_params[:attributes][:'discount-in-percentage'].present? ? product_params[:attributes][:'discount-in-percentage'].to_f : nil # Can be 0.10
if product.discount_in_percentage.present?
product.amount_in_cents = product.amount_in_cents + (product.amount_in_cents * product.discount_in_percentage)
else
product.amount_in_cents = product.amount_in_cents
end
if product.save
# ...
end
end
In Rails, we have a convention that the attributes in Model should be fetched to the Rails App like user[first_name] , user[last_name] and in controller we build a private method like users_params which will represent the data to be fed to the User model. like
# in controller
def update
user = User.find(params[:id])
user.update(users_params)
end
private
# This will prepare a whitelisted params data
def users_params
params.require(:user).permit(:first_name, :last_name, ...)
end
No need of this
#user.first_name = user_params[:attributes][:'first-name'].present? ? user_params[:attributes][:'first-name'] : #user.first_name
#user.last_name = user_params[:attributes][:'last-name'].present? ? user_params[:attributes][:'last-name'] : #user.last_name
In your case, you need to reformat the params keys to first_name instead of first-name and so forth. This will help you do your stuff with ease.
Tip: Try to keep it simpler as possible
When I try to register an user, it does not give me any error but just cannot save the user.
I don't have attr_accessible. I'm not sure what I am missing. Please help me.
user.rb
class User < ActiveRecord::Base
has_secure_password
validates :email, presence: true,
uniqueness: true,
format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }
validates :password, presence: true, length: {minimum: 6}
validates :nickname, presence: true, uniqueness: true
end
users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params) # Not saving #user ...
if #user.save
flash[:success] = "Successfully registered"
redirect_to videos_path
else
flash[:error] = "Cannot create an user, check the input and try again"
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :nickname)
end
end
Log:
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"x5OqMgarqMFj17dVSuA8tVueg1dncS3YtkCfMzMpOUE=", "user"=>{"email"=>"example#example.com", "password"=>"[FILTERED]", "nickname"=>"example"}, "commit"=>"Register"}
(0.1ms) begin transaction
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'example#example.com' LIMIT 1
User Exists (0.1ms) SELECT 1 AS one FROM "users" WHERE "users"."nickname" = 'example' LIMIT 1
(0.1ms) rollback transaction
Regarding our short discussion in the comments, it appears that one or two things are happening to cause #user.save to return false:
One of the validation rules are failing
A callback within your model is returning false, thus halting processing and rolling back the transaction
There are a few quick ways to debug the validations. I figured I could describe them so you could learn a few options.
A. Change the call within the if statement to instead use the bang method of save:
if #user.save!
This will cause the app to raise an exception if validation fails, displaying the validation errors within the browser on your screen. In this particular scenario, you'd want to remember to remove the ! after you're done debugging because you probably don't want the final version of your app doing that.
Or...
B. Within the else statement, add this line:
raise #user.errors.to_yaml
This will display the validation errors within the browser on the screen. Of course, remember to remove this line after you're done debugging.
Or...
C. Within the else statement, add this line and then run the form post:
puts #user.errors.to_yaml
This will display the validation errors within your console. You'll want to remember to remove this line after you're done debugging, but it's "less worse" if you happen to forget because at least the extra info is only output to STDOUT.
You may want to try each of these just to get a little practice and to see what your options are in simple debugging scenarios like this.
High chances that error is in password confirmation. You use has_secure_password from Rails, which automagically handles password confirmation for you. And here is the problem - you don't have it before user creation. Thus just add. For details check out similar question on has_secure_password
And check, that you have password_digest:string in users table :)
Trying to write a plug-in for which I need to check if the user has a particular permission .
For example
I am thinking of something like this
if (user.current has "view_private_notes" )
do something
end
Check User model. There is near exact you want:
# Return true if the user is allowed to do the specified action on a specific context
# Action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
# Context can be:
# * a project : returns true if user is allowed to do the specified action on this project
# * an array of projects : returns true if user is allowed on every project
# * nil with options[:global] set : check if user has at least one role allowed for this action,
# or falls back to Non Member / Anonymous permissions depending if the user is logged
def allowed_to?(action, context, options={}, &block)
You can extend existing models by your plugin and add methods you like combining existing:
//init.rb
ActionDispatch::Callbacks.to_prepare do
unless User.included_modules.include?(MyPlugin::UserPatch)
User.send(:include, MyPlugin::UserPatch)
end
end
//user_patch.rb Something like this:
def self.included(base)
base.class_eval do
unloadable
# need to pass context to be able trigger allowed_to method in old way.
has_right_view_project(project,context)
self.allowed_to?({:controller => context[:controller], :action => context[:action]}, project, :global => true)
end
end
Actually it's easy to use existing methods.
if User.current.allowed_to?(:view_private_notes, #project)
puts "i know what you did!"
end
I have a before_validation :do_something, :on => :create in one of my models.
I want to test that this happens, and doesn't happen on :save.
Is there a succinct way to test this (using Rails 3, Mocha and Shoulda), without doing something like:
context 'A new User' do
# Setup, name test etc
#user.expects(:do_something)
#user.valid?
end
context 'An existing User' do
# Setup, name test etc
#user.expects(:do_something).never
#user.valid?
end
Can't find anything in the shoulda API, and this feels rather un-DRY...
Any ideas? Thanks :)
I think you need to change your approach. You are testing that Rails is working, not that your code works with these tests. Think about testing your code instead.
For example, if I had this rather inane class:
class User
beore_validation :do_something, :on => :create
protected
def do_something
self.name = "#{firstname} #{lastname}"
end
end
I would actually test it like this:
describe User do
it 'should update name for a new record' do
#user = User.new(firstname: 'A', lastname: 'B')
#user.valid?
#user.name.should == 'A B' # Name has changed.
end
it 'should not update name for an old record' do
#user = User.create(firstname: 'A', lastname: 'B')
#user.firstname = 'C'
#user.lastname = 'D'
#user.valid?
#user.name.should == 'A B' # Name has not changed.
end
end
You might like the shoulda callback matchers.
The following regex validates a phone number(could have been anything else though), allowing trailing spaces:
validates :phone,
:presence => true,
:format => {:with => /\(?[0-9]{3}\)?[-. ]?[0-9]{3}[-. ]?[0-9]{4}[ ]*\z/}
The reason I want to allow spaces at the end is simple because some users may type it by mistake.
What I want to be able to do is, allow those spaces during validation, but when saving the record, strip away any leading/trailing spaces. In this way, I can allow the user to make a small "mistake" (i.e. spaces at the end) but still save a "completely valid" value (in this case a phone number) saved to the database.
Can this be done automatically (for any model, any field) so that I don't have to specifically trim each field before saving it ?
If you're only doing this for a few fields, the easiest way to accomplish it would be with custom setters:
def phone_number=(val)
self[:phone_number] = val.rstrip
end
But if you want a more generic, use-anywhere setup, I'd suggest writing an ActiveRecord extension - something along the lines of:
class ActiveRecord::Base
def self.strips_trailing_spaces_from(*attrs)
##sts_attrs ||= []
##sts_attrs << attrs
before_save :strip_trailing_spaces
end
def strip_trailing_spaces
##sts_attrs.each do |attr|
val = self[attr]
self[attr] = val.rstrip if val.is_a?(String)
end
end
end
And then for every model you want to use this, you can simply call (Rails "macro" style):
class MyModel < ActiveRecord::Base
strips_trailing_spaces_from :phone_number, :name, :pizza, :etc
# ...classy stuff...
end
Note - this code hasn't been tested, but it should get idea across. Hope it helps!
# strip leading and trailing whitespace in s
# ... simply:
s.strip! # modify s
s.strip # return modified string
# ... or with a regex
s.gsub!(/^\s+|\s+$/, '') # modify s
s.gsub(/^\s+|\s+$/, '') # return modified string