Assign rails form-only value to model's another attribute - ruby-on-rails

I have User model. One of it's attributes is phone_number.
class User < ApplicationRecord
attr_accessor :phone_number
end
In form_for #user, I want to have two inputs: dialing_code and number. Then, when the form is submitted, I want phone_number to be equal to these inputs concatenated together. One of the solution would be to make these inputs as attr_accessor in the model.
Is there another solution? I was thinking to make these inputs variables only exist in the template, but I have no idea how to do it.

Rather than use an attr_accessor, you should use a pattern sometimes referred to as a "virtual attribute." The idea is to make a getter that combines the "real" attributes, and a setter that performs logic and sets the related "real" attributes.
In your case, it might look something like this:
def phone_number
"#{dialing_code}-#{number}"
end
def phone_number=(string)
return unless string.respond_to?(:split)
self.dialing_code = string.split('-').first
self.number = string.split('-')[1..-1].join('-')
end
You'll need to tweak this depending on how you want phone_number to be displayed and how you want to parse a phone_number into its component dialing_code and number, but the code above gives you the general idea.
Now you can make a new user using a phone_number and its dialing_code and number will be set as expected:
>> user = User.new(phone_number: '333-444-5555')
>> user.dialing_code
#> "333"
>> user.number
#> "444-5555"
Alternatively, you can set dialing_code and number individually and still get a phone_number as expected:
>> user = User.new(dialing_code: '333', number: '444-5555')
>> user.phone_number
#> "333-444-5555"

Two(or more) choices: Selection of it depends on how you want to maintain the data and query later.
Save dialing_code, number and phone_number as attributes (and data fields).
In template, ask for dialing_code and number, and construct phone_number in a hook.
before_save :construct_phone_number
def construct_phone_number
self.phone_number = "#{dialing_code}-#{number}" if dialing_code.present? && number.present?
end
Retain dialing_code and number as attr_accessor and construct phone_number(database field) in the same fashion as above. In this case, dialing_code and number are sort of throw away attributes. You can be okay with it, if you are sure that user input will always be fine and you don't need to debug any sort of issues with phone_number later.
Note: You will need to permit these virtual attributes in strong parameters.

Related

query phone number with different format in the model

There's a table named "Person" with attribute id as primary key and phone_number which is from user input so they are formatted in different ways. I need query the id with phone number.
For example, Person.where(:phone_number => 4155332321)
However, the number in the model could be 415-533-2321 or 4155332321. How could I write the query for that?
BTW, I can't change the phone number format in the model. Otherwise, I can convert the phone in the query and model to be the same format.
Thanks
I think you'll need a two-part approach to this.
First, you'll want to also save a "normalized" phone number to the database. This contains no formatting at all -- just numbers. This will require you to add a new column to your database, say, normalized_phone_number and write a before_save callback to store this value.
class Person
before_save :normalize_phone_number
def self.normalize_number(number)
number.gsub(/[^\d]/, '') if string.present?
end
def normalize_phone_number
self.normalized_phone_number = Person.normalize_number(self.phone_number)
end
end
Next, you'll want to write a custom class method to find the person based on a normalized number from user input. Your Person class will now include:
class Person
def self.with_normalized_phone_number(phone_number)
where(normalized_phone_number: normalize_number(phone_number)).first
end
end
You could also write Person.with_normalized_phone_number as an ActiveRecord scope, which would be my preference, but "Using a class method is the preferred way to accept arguments for scopes."

Rails only initialise an object if condition true

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.

Different ways to create an instant of a model

What is the difference between:
user = User.new(name: "David", occupation: "Code Artist")
and
user = User.new do |u|
u.name = "David"
u.occupation = "Code Artist"
end
Doesn't both of them create a new instant of User?
There is one very important difference here. When you pass a hash to the constructor, rails passes this hash to assign_attributes method, which can do some magic around this hash.
Firstly, in rails 4 it can reject the params hash (which is tainted, will work fine otherwise) if it has not got through strong attributes logic. In rails 3 it will reject the whole hash if at least one of the params is not marked as attr_accessible.
In addition, assign_attributes can accept complex data structures in a form they come from the form. I.e. it will accept params like date(3i), date(2i), date(1i), will check the model column date. If it finds it is in fact a date (database column type), it will create a new Date object from those data and will assign it instead.
None of this functionality is available with block code, unless you call assign_attributes there directly.

How to Inspect Validations that exist on ActiveRecord::Base Objects

I want to create a helper method to automatically output text_fields with a :maxlength attribute. I want this maxlegth to be set based on the :max specified in the field attributes validates_length validation, so as to remain DRY.
My question is: Is there a good way to inspect the validations that are present on an objects attribute, and then extract the maximum length from that validation.
I also plan on extracting other things like the regex from validates_format so I can set an attribute on the text_field, which can then be used by js to run client side validations.
Thanks.
Bonus points: Why doesn't Rails Automatically add the maxlength to text fields for us?
In Rails 3 you can call the _validators method on an object to get the list of validators that will run:
t = Ticket.new
t._validators
Well, I don't know if this is a 'good' way, but you can initialize a new object, call #valid? on it and then call #errors to get a hash of attributes and error messages. Then you'd have to parse those errors messages.
user = User.new
user.valid?
errors_hash = user.errors

Rails ActiveRecord Update question

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.

Resources