I've got to programmatically create users via an api -- I'm able to create users passing in the proper params ie:
x={"email"=>"kid#kid.com", "username"=>"levi", "password"=>"password","password_confirmation" => "password", "firstname"=>"Bob", "lastname"=>"Smith"}
u=User.new(x)
u.valid?
And able to check if this is a valid user before saving, etc. But the problem is that if params such as username or email already already exist for another user I'll get an error on save.
Now I can check if a user exists already (ie u=User.where(username: x['username']) or u=User.where(email: x['email']) -- and that might be enough but I'm wondering if there's a way of doing u=User.where(x).first_or_create with devise? Works generally with other models but not devise.
The problem with find_or_create_by (http://apidock.com/rails/v4.0.2/ActiveRecord/Relation/find_or_create_by) is that it will try to find a user matching all of the attributes you pass in as criteria. It is rarely a useful method. Instead, wrap it in your model:
class User < ActiveRecord::Base
def self.username_find_or_create(attributes = {})
User.create(attributes) unless User.exists?(attributes.slice(:username, :email))
end
end
(Exists is faster than where)
Related
I am trying to make an app in Rails 4.
I am using devise and want to write an after sign up redirect path based on the following logic.
I have a user model that includes an :email attribute. I have an organisation model that includes an :email_format attribute (email attribute holds the part of the email address that comes after '#'. I have a profile model (which contains the user's information that can be altered by the user itself).
The associations are:
User - has_one: profile
Profile - belongs_to :user, belongs_to: organisation
Organisation - has_many :profiles
If the user (registering) inputs an email address that includes an email format that is saved in the db, then I want to associate that new user's profile with the organisation that has the matching email format.
Example:
New user's :email = bob#cat.com
I then check the organisation table to see if any :email_attributes stored are 'cat.com'.
If they are, then the user profile for the new user, is associated with the organisation that has the matching email_format (so the organisation_id in the relevant profile table is set to match that organisation id).
Here's my best attempt at writing this:
registrations controller:
def after_sign_up_path_for(resource)
# check if the email address the user used to register includes the email format of an organisation
if #user.email.includes? #organisation(:email_format)
# if it does match, then update the user profile so that the org id equals the org id for the org that has the matching email format
#user.profile.organisation_id.update_attribute(organisation.id == (#organisation.email includes? :email_format))
else
# set up a mandrill email sender to send this message
set_flash_message('We are just getting started and will get in touch with
you as soon as we have onboarded your organisation')
end
end
I'm new to this and don't understand how to query things well. Any pointers would be very much appreciated.
NEXT ATTEMPT
Taking the suggestion below, I've tried to add an after_create callback to my profile model as follows:
after_create :update_organisation
def update_organisation
Profile.update_attribute(organisation_id: :matching_org_id) # Associations must be defined correctly for this syntax, avoids using ID's directly.
# Profile.save
end
def matching_org_id
if #self.user.email.includes? #organisation(:email_format)
# how do you ask for the organisation with the matching email format id
profile.organisation_id: organisation.id
else
TBC
end
end
This isn't working. I'm not sure how to express this method correctly. I'm currently getting an error that says:
syntax error, unexpected '(', expecting keyword_then or ';' or '\n' if #self.user.email.includes? #organisation(:email_format)
First problem:
This part is fairly dangerous: you could have bobcat.com#gmail.com return a false positive.
if #user.email.includes? #organisation(:email_format)
Perhaps you can use store a regex format and check it via
/#cat.com$/ =~ #user.email
Try out the formats at rubular.com
Second problem:
The update method should be something similar to this.
#user.profile.update(organisation_id: #matching_organisation.id)
You will need to loop through all organisations to find the matching organisation.
Third Problem
I think this is a bad place to do this sort of manipulation. You should perhaps add an after_create hook to the user model and put these logic in that method.
I have successfully implemented an invite feature in my app. Each user has_many scoreboards and each scoreboard has_many invitations. The invitation model has two columns, recipient_name and recipient_email and an invitation email is sent to the recipient_email. All these features are working perfectly fine. The controller code for the create action is given below.
** scoreboard
** has_many :sent_invitations, :class_name => "Invitation"
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#invitation = #scoreboard.sent_invitations.build(invitation_params)
if #invitation.save # and the recipient_email doesn't exist in the user model database
# send the sign_up email
UserMailer.invitation_email(#invitation, #scoreboard).deliver_now
flash[:success] = "Invitation sent"
redirect_to new_scoreboard_invitation_path
#elsif
# if the user exists in the database
# send the email saying they've been invited to view a scoreboard
else
render new_scoreboard_invitation_path
end
end
end
As an extension of the feature, I also want to query the database to check if the recipient_email provided in the invitation model exists in the user model (column :email). I have emails set as unique when the user signs up, therefore, searching for the email will reveal whether the user is registered or not.
The problem is that I am not sure how to check if the recipient_email present in the invitation_table also exists in the user_table. I have tried several things.
I have tried saving the recipient_email's most recent record in a local variable and then querying the database for that record. I didn't think that was the correct way to implement this.
I also tested the code given below in the invitation's new action with a simple if and else statement to see what would happen. However, every time I send an email to a registered or non-registered user, It always prints "no". I am not exactly sure exactly how to approach this correctly. I know the exists? method would be used somewhere but not sure how to really use it. I have tried to keep it to the point and included the relevant pieces of code. However, if I missed anything, I can definitely include that. Any help would be greatly appreciated, Thanks!!
<% if User.where("email = ?", #invitation.recipient_email).exists? %>
<%= "Yes" %>
<% else %>
<%= "no" %>
<% end %>
You're on the right track with your usage of the .exists? method, but you're actually calling it incorrectly. The condition (which you have as a .where statement) is actually meant to be passed to the .exists? function as a parameter.
So, instead of saying:
if User.where("email = ?", #invitation.recipient_email).exists?
You actually want to say:
if User.exists?( email: #invitation.recipient_email )
.exists? takes a hash of field names paired with values. For more detail on the exists? method, see the official documentation.
Addendum
Regarding the hash notation (being passed to the .exists? method), in the most recent versions of Rails the "standard" way of passing parameters to ActiveRecord methods is in hash form. There are, however, certainly times when it is appropriate to use the question mark interpolation method you chose to employ. I'm only offering this comment to alleviate any confusion between the two different method calls. Each of the following would perform the same query:
.where("field = ?", value)
.where(:field => value)
.where(field: value)
In my model User, there already exists an id column which is auto-incremented. Now I plan to have a unique name for the user.
Now, the user won't be asked for it in the sign up process, so by default, I would like the column to return "user" + id when called like
user.profile_name #=> should return "user#{user.id}" if nil
I don't want to duplicate the data, so I would want to keep the field nil until the user enters one.
So, I thought of creating a custom function(or overriding the profile_name if possible)
# model User.rb
def get_identifier
profile_name ? profile_name : "user#{id}"
end
And using where to find the user
id, user = params[:identifier], nil
if id[0..3] == "user"
user = User.find_by_id(id[3..-1].to_num)
else
user = User.find_by profile_name: id
But this seems not to be a rails way. I would have to take care of both the cases even when querying.
Is there any way to simplify this in Rails that I'm missing? Else can the current approach be bettered (apart from refactoring the code into methods)?
P.S The question is tagged appropriately with the versions I'm using. Ruby version - 2.1.5
I'd like to preface with this answer with my opinion that your hesitation to "duplicate the data", where the data is really just the id, might be uncalled for. It's not user-editable or anything, so I'm not sure what the downsides are if you're not counting bytes in your database size.
Given the constraints of the question, I'd look at an implementation like this:
class User
def self.find_by_profile_name(profile_name)
where(profile_name: profile_name).first || find_by_default_profile_name(profile_name)
end
def self.find_by_default_profile_name(profile_name)
where(id: DefaultProfileName.from_profile_name(profile_name).id)
end
end
class DefaultProfileName
attr_accessor :id
def self.from_profile_name(profile_name)
new(profile_name.sub('user', ''))
end
def initialize(id)
self.id = id
end
def to_s
"user#{id}"
end
end
I'm sure this could be improved on, but the main takeaway is that encapsulating your default profile name functionality in its own class enables making this bit of functionality contained and more manageable.
I would argue that you need to backfill profile_names for existing users. Otherwise you might run into all kinds of interesting problems.
For example: There are the existing users 1 and 2, both do not have a unique username yet. User 1 has the profile_name user1, analog for user 2 which has user2 as the profile_name user2. Now user 1 decides to set a profile_name and he sets its profile_name to user2, what is a unique profile_name at the moment - at least from a Rails validator's point of view.
To avoid this problems: Always set a unique profile_name and backfill profile_names for existing users.
I have the following problem, but first I will make some assumptions
The example is just to explain my problem in an easy way
The tables models are not related
I have two tables (models) Users an Emails
Users
id
name
email
Emails
id
account
So, the idea is every time I create a user, I want to create an instance of Email, where Emails.account = Users.email
I tried using callback
def after_create
Email.create!(:account => user.email)
end
But it didn't work.
Is there another way to achieve this?
You are almost there, except you don't need to reference a 'user' variable in your after_create because you are in the User model.
Try the following:
class User < ActiveRecord::Base
after_create :create_email
def create_email
Email.create!(:account => email)
end
end
I have a user model which has multiple addresses. Now for my application in rails, address is not mandatory. So, if someone wants to create a user and enter the address after the user has been created, my application should allow that. My problem is, for Address model I have validations for Address Line 1, City and Postal Code. These fields cannot be blank. When, editing a user, the following code fails:
user.addresses << Address.new
Rails tries to create a new Address and fires an Insert command. This is going to fail because of the validations that is required in the model. The above code doesn't fail if the user is not present in the database. One solution to this problem is to create a separate form_for binding for the edit partial for user. I don't want to do that solution. Is there any solution that allows me to bind an empty Address object for an already existing User object in the database ?
Why attempt to add an empty Address object to the user.addresses collection? I think you could simply do something like:
user.addresses << Address.new unless (conditions)
I unfortunately don't know what your conditions are here, so it could be something like
user.addresses << Address.new unless params[:address].nil?
...although my guess is that you have a real Address object instead of just passing in a blank Address.new...
user.addresses << Address.new
This code isn't going to work anyway if your Address model requires its fields to be set, because you're not supplying a hash to Address.new
If you want to add the address conditionally, you probably want something like this:
if !params[:address].blank?
user.addresses.create(params[:address])
end
or
user.addresses << Address.new(params[:address]) unless params[:address].blank
If you really want to create an "empty" address object for each user (instead of just having users without addresses), you can change your validations so they only fire if the fields are filled out.
Something like this:
class Address < ActiveRecord::Base
validates_presence_of :address1, :if => :non_empty_address?
# etc
private
def non_empty_address?
!address1.blank? || !address2.blank || !city.blank? # etc
end
end
The restful_authentication plugin uses a similar approach to determine if the user's password is required.