How to change permit parameter before saving in Rails? - ruby-on-rails

I have the next code:
permitted = params.permit(:url, :title, :description, :post_type, :category_id)
and I want to change params[:url] before saving process. I did try:
params[:url] = "abc"
but it didn't change this value. How can I catch and change it before saving? It's into feed controller. I also tried:
params[:feed][:url]
but again nothing. Can anyone gives me some tips?

You can initialize the model with permitted parameters, then change any of them how you wish:
m = Model.new(permitted)
m.url = "abc"
m.save

I use the following ways, depending on what I need.
before_save callback
Define in your model. Good when there is a general rule on what data should be in the given field. In most other cases too, actually, but that's slightly more complicated.
params.require(...).permit(...).merge(url: 'whatever')
This takes your parameters and writes (overwriting) the ones given inside merge. It doesn't have to be one key-value pair. I use that sometimes for writing in timestamps. It's a rather clean trick, but befoe_save should be favored: I only use it when I don't think writing a separate one-use-case method on a model is worth it.

Related

Can I append attributes to Ruby OpenStruct on the go?

I am new to Ruby and this is a really basic question, when I searched for adding/appending values to OpenStruct, I couldn't find any resource.
I'm trying to wrap the response body with extra params and the code in place uses OpenStruct. Now I need to append some key/value later in the code before sending the final reponse.
OpenStruct.new(
body : api_response.body
check1? : true
)
I want to add check2? : false.
The whole point of OpenStruct is that you can add new fields on the fly.
response = OpenStruct.new(
body: 'foo',
check1: true
)
response.check2 = false
p response
# => #<OpenStruct body="foo", check1=true, check2=false>
This is the only advantage that it has over Struct. Using OpenStruct incurs a considerable performance penalty, so if you don't need to add new fields later, it should never be used (unless of course you absolutely don't care about performance); use Struct instead.
However, specifically in your case, Ruby's parser does not allow methods of form check1?=, as both the question mark and the equality sign are only permitted at the end of the identifier; i.e. check1= is a valid method name, check1? is a valid method name, but check1?= is not.
tl;dr: Drop the question mark.
There are two ways to do it depending on what works best for the use case.
One can either do a quick fix with something like this
openstruct_object.check2? = false
OR an elegant way of doing it is to wrap the creation of your OpenStruct instance in a method that accepts the check2? param. (This is what I did and it works great with named args!)
def wrap_reponse(body, check1 = "your_default", check2: "named_args")
OpenStruct.new(
body : body,
check1? : true,
check2? : false
)
end
There is a good blog for reference, which I got after considerable google search.

Is there a more idiomatic way to update an ActiveRecord attribute hash value?

Given a person ActiveRecord instance: person.phones #=> {home: '00123', office: '+1-45'}
Is there a more Ruby/Rails idiomatic way to do the following:
person_phones = person.phones
person_phones[:home] = person_phones[:home].sub('001', '+1')
person.update_column :phones, person_phones
The example data is irrelevant.
I only want to sub one specific hash key value and the new hash to be saved in the database. I was wondering if there was a way to do this just calling person.phones once, and not multiple times
Without changing much behaviour:
person.phones[:home].sub!('001', '+1')
person.save
There are a few important differences here:
You modify the string object by using sub! instead of sub. Meaning that all other variables/objects that hold a reference to the string will also change.
I'm using save instead of update_column. This means callbacks will not be skipped and all changes are saved instead of only the phones attribute.
From the comment I make out you're looking for a one liner, which isn't mutch different from the above:
person.tap { |person| person.phones[:home].sub!('001', '+1') }.save
You can use the before_validation callback on your model.
Like this:
class Phone < ApplicationRecord
validates :home, US_PHONE_REGEX
before_validation :normalize_number
private
def normalize_number
home.gsub!(/^001/, '+1')
end
end
Note: I haven't tested this code, it's meant to show an approach only.
If you're looking to normalize also an international number, evaluate if the use of a lib like phony wouldn't make more sense, or the rails lib https://github.com/joost/phony_rails based on it.
EDIT
since the comment clarify you only want to change the values of the hash in one like you can use Ruby's method transform_values!:
phones.transform_values!{|v| v.gsub(/^001/, '+1')}

Should we use strong params when we update only one attribute?

I'm working on a Rails app and I have several actions( #delete_later, #ban_later and so on) where I only set one attribute from the request parameter( specifically, a reason field for doing that action).
I was wondering if it is ok to do it like this:
def ban_later
#object.reason = params[:object][:reason]
#object.save
end
Or is it a best practice to use strong params even in this situation?
def ban_later
#object.reason = object_params[:reason]
#object.save
end
private
def object_params
params.require(:object).permit(:permitted_1, :permitted_2, :reason)
end
Which of these solutions is the best one? If none of them is, then what's the best solution to my problem?
Later Edit:
The #ban_later, #delete_later actions can indeed set a flag column status but that can be done without receiving it's value from the params hash. Since you will only set one status per method you can simply set the status "pending_delete" when you are in #delete_later and "pending_ban" when you are in #ban_later.
Later Later Edit
Why use #save and not update_attributes directly? Let's say you need to have a if #object.save statement. On the false branch( object not saved) you might still want to render a view where the contents of that #object are used.
First one saves computation.
Second one checks for existence of :object sub-hash, which I think is good for fault-tolerance.
I initially would pick the 1st, but after some thought I liked the second one more.
The simplest answer is that if you only use one parameter in params, and do not pass it to a multi attribute setter like model#create then you don't have to use strong_parameters to get a secure solution.
However, I expect that it is unlikely that this is the case for the whole controller. Where the ban_later method only needs one parameter, other controller methods will need more. In this case the question becomes: "do you want to handle params differently for ban_later to how you use it for the other controller methods?".
Also can you be sure that the functionality will not change, and that when you change the functionality, that you'll remember to change the way params is handled.
Therefore, I would use strong_parameters because it means:
parameters are handled consistently across all methods in the controller.
changes to methods are less likely to expose vulnerabilities as functionality changes.
If you're updating a single attribute, why don't you use the update_attributes method? (update_attribute doesn't invoke validation)
def ban_later
#object.update_attributes reason: params(:reason)
end
private
def params params
params = %i(:permitted_1, :permitted_2, :permitted_3) unless params
params.require(:object).permit params
end
In light of the comments by ReggieB, you could also use the update option:
def ban_later
#object.update reason: params(:reason)
end
As mentioned, Reggie and the other answers explain the schematics of how this works best (IE with mass-assignment etc). Above is actionable code which you're free to use.
The bottom line here is that if you want to keep your application versatile (IE having ultimate extensibility wherever you need), you'll need to adhere to the strong params setup.
The other answers outline how that setup works, and how its functionality is different dependent on what you need.
I have included a trick to make it so you only accept specific params in your params method. I've not tested it extensively, so we may have to refactor it to get the required result.
After strong parameters check why not just update the object? Its just a standart workflow. (Please tell me if there are any reasons not to do that in your situation)
def ban_later
#object.update(object_params)
# dont forget validation check
end
private
def object_params
params.require(:object).permit(:permitted_1, :permitted_2, :reason)
end
In this case it'd be much easier to add more updateble fields.

Rails: Convert string to variable (to store a value)

I have a parameter hash that contains different variable and name pairs such as:
param_hash = {"system_used"=>"metric", "person_height_feet"=>"5"}
I also have an object CalculationValidator that is not an ActiveRecord but a ActiveModel::Validations. The Object validates different types of input from forms. Thus it does not have a specific set of variables.
I want to create an Object to validate it like this:
validator = CalculationValidator.new()
validator.system_used = "metric"
validator.person_height_feet = 5
validator.valid?
my problem right now is that I really would not prefer to code each CalculationValidator manually but rather use the information in the Hash. The information is all there so what I would like to do is something like this, where MAKE_INTO_VARIABLE() is the functionality I am looking for.
validator = CalculationValidator.new()
param_hash.each do |param_pair|
["validator.", param_pair[0]].join.MAKE_INTO_VARIABLE() = param_pair[1]
# thus creating
# "validator.system_used".MAKE_INTO_VARIABLE() = "metric"
# while wanting: validator.system_used = "metric"
# ...and in the next loop
# "validator.person_height_feet".MAKE_INTO_VARIABLE() = 5
# while wanting: validator.person_height_feet = 5
end
validator.valid?
Problem:
Basically my problem is, how do I make the string "validator.person_height" into the variable validator.person_height that I can use to store the number 5?
Additionally, it is very important that the values of param_pair[1] are stored as their real formats (integer, string etc) since they will be validated.
I have tried .send() and instance_variable_set but I am not sure if they will do the trick.
Something like this might work for you:
param_hash.each do |param, val|
validator.instance_eval("def #{param}; ##{param} end")
validator.instance_variable_set("##{param}", val)
end
However, you might notice there's no casting or anything here. You'd need to communicate what type of value each is somehow, as it can't be assumed that "5" is supposed to be an integer, for example.
And of course I probably don't have to mention, eval'ing input that comes in from a form isn't exactly the safest thing in the world, so you'd have to think about how you want to handle this.
Have you looked at eval. As long as you can trust the inputs it should be ok to use.

Rename ActiveResource properties

I am consuming JSON data from a third party API, doing a little bit of processing on that data and then sending the models to the client as JSON. The keys for the incoming data are not named very well. Some of them are acronyms, some just seem to be random characters. For example:
{
aikd: "some value"
lrdf: 1 // I guess this is the ID
}
I am creating a rails ActiveResource model to wrap this resource, but would not like to access these properties through model.lrdf as its not obvious what lrdf really is! Instead, I would like some way to alias these properties to another property that is named better. Something so that I can say model.id = 1 and have that automatically set lrdf to 1 or puts model.id and have that automatically return 1. Also, when I call model.to_json to send the model to the client, I dont want my javascript to have to understand these odd naming conventions.
I tried
alias id lrdf
but that gave me an error saying method lrdf did not exist.
The other option is to just wrap the properties:
def id
lrdf
end
This works, but when I call model.to_json, I see lrdf as the keys again.
Has anyone done anything like this before? What do you recommend?
Have you tried with some before_save magic? Maybe you could define attr_accessible :ldrf, and then, in your before_save filter, assign ldrf to your id field. Haven't tried it, but I think it should works.
attr_accessible :ldrf
before_save :map_attributes
protected
def map_attributes
{:ldrf=>:id}.each do |key, value|
self.send("#{value}=", self.send(key))
end
end
Let me know!
You could try creating a formatter module based on ActiveResource::Formats::JsonFormat and override decode(). If you had to update the data, you'd have to override encode() also. Look at your local gems/activeresource-N.N.N/lib/active_resource/formats/json_format.rb to see what the original json formatter does.
If your model's name is Model and your formatter is CleanupFormatter, just do Model.format = CleanupFormatter.
module CleanupFormatter
include ::ActiveResource::Formats::JsonFormat
extend self
# Set a constant for the mapping.
# I'm pretty sure these should be strings. If not, try symbols.
MAP = [['lrdf', 'id']]
def decode(json)
orig_hash = super
new_hash = {}
MAP.each {|old_name, new_name| new_hash[new_name] = orig_hash.delete(old_name) }
# Comment the next line if you don't want to carry over fields missing from MAP
new_hash.merge!(orig_hash)
new_hash
end
end
This doesn't involve aliasing as you asked, but I think it helps to isolate the gibberish names from your model, which would never have to know those original names existed. And "to_json" will display the readable names.

Resources