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.
Related
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)
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)
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.
Is there an approach to conditionally prevent the creation of an object.
I have a Person class which has_many :contacts. If I try to initialize a Contact without providing an address, then the contact should not be created.
I can do this within a person object:
person.contacts << Contact.new(params[:contact]) if params[:address].present?
But can I do this within the Contact class? i.e. preventing the addition of a new Contact without the if condition above.
The reason for the question is that if I have contact fields on a form each with an address and their own contact_type in a hidden field, then the contact object would be created even if the address field is not populated.
UPDATE
Following further thoughts, the following are other options I have considered, but all have downsides:
remove from the params any contacts which do not include address:
Within the strong params method to iterate the params hash and remove any references to contacts without address params[:person][:contacts_attributes].delete_if { |key, value| value[:address].blank? }. This works, but obviously with a polymorphic model could be DRYer.
Create factory method within the Person and Business objects to define to assess the incoming request for a Person to be created and remove any contacts without address. I assume that this could be made DRY by abstracting into in a module, but this feels rather complex for this scenario.
At present option 1 above is what I am going to go with, but I'd be really interested if there is something that can be done in the Contact object.
This is based on the comment above that you do have a validation on the presence of the address field
Seems like there should be a better way to do this but, does this work for you?
new_contact = Contact.new(params)
person.contacts << new_contact if new_contact.valid?
update:
probably the right way to do this is like this
begin
person.contacts.create! params
rescue ActiveRecord::RecordInvalid => e
end
It should probably be in the controller
In the create method
def create
#contact = Contact.new(params.require(:address).permit(:phone number, contact_type)
<end>
This will not allow a contact to be created without an address but will allow a phone number and a contact type empty or not.
I believe that the answer lies in the following:
accepts_nested_attributes_for :contacts, allow_destroy:true, reject_if: proc { |attributes| attributes[:address].blank? }
If I add the reject_if proc, then any submissions without an address seem to be ignored.
Here's the basic setup:
I have an Order model. An Order has one Address and it accepts_nested_attributes_for :address.
I have a basic order form where I ask a user to input her address. This is handled with nested_fields_for. Everything works great - new addresses are validated and assigned nicely.
However, the problem is that it creates a new Address every time, even if an Address already exists with identical attributes.
I would like to modify the behavior so that if the user-inputted address matches all the attributes for an existing Address, the order assigns the existing Address to itself rather than creating a new one.
The methods I have tried are:
In the controller, try to find an existing Address record with the nested attributes (params[:order][:address_attributes]). If a match exists, delete all the nested attributes and replace them with params[:order][:address_id].
Don't use nested_attributes_for at all and instead override the address= method in the model, then just use the controller to create a new Address based on the parameters and then hand it off to the model.
Both of these solutions seem various degrees of messy. Could somebody please enlighten me on whether this is a controller or model responsibility, and perhaps suggest an elegant way to accomplish this?
Thanks in advance.
Have you tried something like this?
class Order < ActiveRecord::Base
# [..]
before_save :replace_existing_address!
def replace_existing_address!
db_address = Address.where(:city => self.address.city,
:street => self.address.street,
:number => self.address.number).first
self.address = db_address if db_address
end
end
Since I'm asking this as a survey of good ways to do this, I figured I'd offer the solution I'm currently using as well as a basis for comment.
In the Controller:
#new_address = Address.new( params[:order][:address] )
#order.address = new_address
#order.update_attributes( params[:order] )
In the Model:
def address=( address )
return unless address and address.is_a? Address
duplicate_address = Address.where( address_1: address.address_1,
address_2: address.address_2,
[etc. etc.] ).first
if duplicate_address
self.address_id = duplicate_address.id
else
address.save
self.address_id = address.id
end
end
I it's truly a :has_one relationship as you say and not a :has_many, you don't need to explicitly assign the address like you do in your own answer. That's what accepts_nested_attributes is for, after all. This line by itself should work:
#order.update_attributes( params[:order] )
That should create a new address if none exists, and update an existing one.
Your solution may work, but it a) doesn't take advantage of accepts_nested_attributes and b) will leave lots of orphaned addresses in your database.