Different ways to create an instant of a model - ruby-on-rails

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.

Related

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

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.

Shortcut for including strong parameters in Rails 4 : without listing all fields

I am starting with Rails 4. Had came across to the new security feature strong parameters related to permitting parameter in a controller.
http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html
This is fine, but we need to list down all the fields from the models. Is there a easy way by which listing fields down the is not required.
Thanks.
One shortcut you can do if you have say around 20 columns in your model and you don't need to specify all columns inside permit in your controller then it as:
params.require(:person).permit!
This will permit all the attributes from your model
Here's a quick "getting started" tip regarding cases where you have "lots" of fields for your model Foo... rather then whitelist them all, auto-generate a list then remove the ones you know should not be mass-assigned.
In rails console you can get a symbol list of all fields very easily, copy/paste it to the permit(...) method, then remove the ones that should not be mass-assigned like :id, :ctreated_at, :admin, etc
Foo.attribute_names.map(&:to_sym).sort
> [:address, :admin, :created_at, :id, :name, :updated_at]
This takes only a few seconds longer than using the .permit! approach, but gives you a better starting point from a security point of view.
Strong Parameters were introduced in Rails 4:
It provides an interface for protecting attributes from end-user
assignment. This makes Action Controller parameters forbidden to be
used in Active Model mass assignment until they have been whitelisted.
Basically, it means only certain param values will be sent through your controller to the model (thus allowing you more control over which data is handled by Rails)
DRY
If you're wanting to use strong params for multiple controllers, or just want to DRY up the process, we use this:
#app/controllers/application_controller.rb
private
#Strong Params
def permitted_params
#resource = self.resource_class
#model = "#{#resource}".downcase.to_sym
attributes = #resource.attribute_names + %w(custom items here)
params.permit(#model => attributes)
end

Replace sensitive elements in a params hash to avoid mass assignment in Rails

I would like to have a more systematic solution for myself to avoid mass assignment.
A typical situation is to remove id or user_id from params (submitted automatically via form) and replace it with current_user.id internally (in MyController#create).
A solution I can think of is to create object from params hash, then update_attributes (of parent and child objects) to replace sensitive attributes with internal values:
#user = User.create(:params[:user])
#user.update_attributes(:id => current_user.id)
#user.profile.update_attributes(:user_id => current_user.id)
#user.preference.update_attributes(:user_id => current_user.id)
Is there a shorter/more DRY way to say this?
If preference, profile etc. are child objects of user (created via build method), how can I write a method to look for their foreign keys for user and automatically replace them with the value I passed to parent?
Thank you.
This is what attr_protected and attr_accessible (documentation) are for. attr_protected will give you blacklist protection, while attr_accessible will protect using a whitelist.
While calling update_attributes right after a mass assignment would work, you're better off using the built in ways of protecting mass assignments as it won't require duplication of code every time you do a mass assignment on a model.
I've done this in an earlier project by using:
#user = User.create(params[:user]) do |user|
user.id = current_user.id
end
Would this work for you?
An alternative is to check out the docs and search for the :as for role based attributes.

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

Creating model objects from raw form data - is a one-to-one field match required?

If I say this in the controller:
#order = Order.new(params[:order])
What is required for this to work?
Does there need to be a one-to-one match between all of the fields in params[:order] and the Order model?
Or can there be more or fewer fields in params[:order] than are required to instantiate an Order?
params[:order] itself should be a hash, where each key is the name of the model field. To see how Rails converts form field names into the params hash, write a view template with the form_for helper and view source.
There can be more or fewer fields, yes. Extra fields will be ignored. Fewer fields just won't be copied into the instance object. You don't need anything at all to instantiate an ActiveRecord object. (Object validity and saving are a different story - they invoke validations and the ActiveRecord callback mechanism.)
There can indeed be fewer fields.
Make sure you have all fields necessary for any validations though!

Resources