Can I append attributes to Ruby OpenStruct on the go? - ruby-on-rails

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.

Related

Rails 4 parameters; how to whitelist a param to a set of values

I have already read some posts, such as Value whitelist using strong parameters in Rails 4, but it's not quite what I need.
I have a controller which takes an ID and loads a model. It also optionally takes a query string param (style) which can be one of 3 values, small, medium or large. This is passed to a method on the model which uses it to fetch an attached image (using paperclip). I noticed that if I pass an invalid param (eg style=wibble), then I get a 400 error and a notice that the internal file path doesn't exist. Brakeman also notes this as a security issue...
def show
style = params[:style] || :medium
thing = Model.find(params[:id])
path = "#{Rails.root}/public#{thing.image_url(style)}"
content_type = thing.image.content_type || 'image/png'
send_file path, type: content_type, disposition: 'inline'
end
We use ActionController Parameters elsewhere to great effect; but I cannot see how that could "whitelist" a parameters options? Everywhere I have seen says to use a model validator, but that's assuming I'm submitting a parameter to update a model, which I am not.
I'm aware I could do something like:
return head :not_found unless %w(small medium large).include? style
Is this the best way?
I wrote a gem that extends ActionController::Parameters for exactly this purpose: whitelisting parameter values. It's simple and lightweight. You would use it like this:
def model_params
params.require(:model).permit(:style, :other_attribute).allow(style: %w[small medium large])
end
Hope you find it useful
https://github.com/msimonborg/allowable
First, you need to define a Constant white-listing all the valid style values (I would argue that size_type is a more explicit name). (Watch out for the Symbol / String comparisons).
Then, you either return a 404 if the params[:style] is not included in the white-listed values OR you fallback to 'medium':
style = Model::AVAILABLE_SIZE_TYPES.include?(params[:style]) ? params[:style] || Model::AVAILABLE_SIZE_TYPES.first
Personally, I don't like fallbacks for user's input. They did something wrong (manipulated the URL, changed a value in a form, etc), they deserve to get an error page.
I implied the constant is defined in the Model, but it should not be there as it is not related to the business logic but related to the display of the Model. If you have a ModelDecorator, define it there.

How to add new Select2 option terms to the server?

In a Rails 4 app I am using Select 2 v4.0 to display a typeahead-style select field. Select2 generates the options from an ajax call to a json API within the app.
This is working great.
I now want to allow users to create a new item if their entry does not already exist in the json.
This is easy to set up on the client side
$('.my-field').select2
tags: true
But what is the best way to handle this on the server?
My original thought was that, if an existing options is selected, the form would return an ID integer, whereas if a new item is selected, a string would be returned. I could then call a setter method in the controllers create method
def create
#object = Object.build(params)
if params[:my_field_id].respond_to?(:to_str)
#object.setter_method = params[:my_field_id]
params[:my_field_id] = nil
end
..
end
But of course even IDs are treated as strings in the params.
Next I thought I could modify the string so that new items can be distinquished. Select2 makes this relatively easy.
$('.my-field').select2
tags: true
createTag: (tag) ->
{
id: '<<<<' + tag.term + '>>>>'
text: tag.term
}
Then in the controller I could do something like
def create
#object = Object.build(params)
regex = my_regex_pattern
if params[:my_field_id].gsub(regex)
term = params[:my_field_id].slice('<<<<').slice('>>>>')
#object.setter_method = term
params[:my_field_id] = nil
end
...
end
So my questions:
Does this seem like a sensible approach, or am I overlooking a
better way?
This approach seems like it could be quite brittle if, for example,
the new item contains '<<<<'. Do I need to worry to about this?
My regex knowledge is poor to none. What is the best source of
information to ensure that my_regex_pattern matches to <<<<a
random string of text>>>>.
Are the slice methods the best way to remove these extra
substrings?
EDIT
After a little playing, it appears that slice might be improved upon as follows:
term = params[:my_field_id].reverse.chomp('<<<<').reverse.chomp('>>>>')
It is a bit unwieldily and not the best readability. Is there a better way?

How to change permit parameter before saving in 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.

How to retrieve all attributes from params without using a nested hash?

I am currently in the process of making my first iphone app with a friend of mine. He is coding the front end while I am doing the back end in Rails. The thing is now that he is trying to send necessary attributes to me with a post request but without the use of a nested hash, which means that that all attributes will be directly put in params and not in a "subhash". So more specifically what I want to do is be able to retrieve all these attributes with perhaps some params method. I know that params by default contains other info which for me is not relevant such as params[:controller] etc.. I have named all attributes the same as the model attributes so I think it should be possible to pass them along easily, at least this was possible in php so I kind of hope that Rails has an easy way to do it as well.
So for example instead of using User.new(params[:user]) in the controller I have the user attributes not in the nested hash params[:user] but in params directly, so how can I get all of them at once? and put them inside User.new()?
I found the solution to my problem. I had missed to add the attr_accessible to my model which was what initially returned the error when I tried to run code like: User.new(params) having been passed multiple attributes with the post request.
The solution was very simple, maybe too simple, but since this is my first real application in Rails I feel that it might be helpful for other newbies with similar problems.
If you would like to just pass a more limited version of params to new, you should be able to do something like the following:
params = { item1: 'value1', item2: 'value2', item3: 'value3' }
params.delete(:item2)
params # will now be {:item1=>"value1", :item3=>"value3"}
Also see this for an explanation of using except that you mention in your comment. An example of except is something like:
params.except(:ssn, :controller, :action, :middle_name)
You can fetch the available attributes from a newly created object with attribute_names method. So in this special example:
u = User.create
u.attributes = params.reject { |key,value| !u.attribute_names.include?(key)
u.save

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.

Resources