How to add new Select2 option terms to the server? - ruby-on-rails

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?

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.

How do I generate all new records for a given model randomly using pre-defined options?

I'd like one of my models, Stones, to be generated at random using pre-defined options I've stored in a set of arrays and hashes. Instead of Create using params from the URL, I'd like new Stones to always be defined using this random generation process. I don't need any user input at all, except that each stone belongs to a given player.
I'm still new to rails; where should I put all this code? I know enough to be able to define the arrays and hashes and randomly select from them when I need to, but I'm not sure where and how to replace the part of the code that draws params from URLs and fills in a new record before it is saved. I know controllers are supposed to be skinny, so do I do this in the model?
Apologies if this is a duplicate. I searched extensively and couldn't find an applicable solution.
Thanks for any help!
I would create a service for this. Something like:
# app/services/stone_creator.rb
class RandomStoneCreator
RANDOM_FOOS = ['bar', 'baz', 'bat']
def self.call(user)
Stone.create!({
foo: RANDOM_FOOS.sample,
user: user
})
end
end
And then anywhere that you need a new random stone you can call it like:
random_stone = RandomStoneCreator.call(current_user)

Setting virtual model attribute from controller create action in Rails 4 - attribute always nil

Some background: I have a Rails 4 model with a decimal column d. When being saved or viewed in the app, the value may need to be converted to or from an integer value, respectively, based on the current user's preference.
It seems most appropriate that the calculations should go in the model, and should have no knowledge of users.
After doing some searching, it seems to me the best way to achieve this is via 2 virtual attributes on the model: one for the current user's preferred format and one for the value, with any calculation applied.
So I have come up with something like this:
attr_accessor :format, :value_converted
def value_converted
if format.nil?
format = 'A'
end
if format == 'A'
Converter.new(d).to_i if d
else
d if d
end
end
def value_converted=(value)
if format.nil?
format = 'A'
end
if format == 'A'
self.d = Converter.new(value.to_i).to_f
else
self.d = value
end
Forms refer to the value_converted rather than the d, and this works fine for editing, updating and viewing. The problem is the #create action.
I believe the issue is happening because the setter is accessing format, and I can't seem to figure out a way to set format from #create before the setter is called, in other words when new creates the object.
I thought perhaps I could just pass the format along with the other parameters (as in this answer, so I tried merging it in, both within and after the standard strong parameters fare, but with no luck:
Both
#entry = current_user.entries.new(entry_params.merge(format: current_user.format))
and
params.require(:entry).permit(:value_converted, :other_param, ...).merge(format: current_user.format)
do not raise any errors, but are apparently ignored. However simply passing format to new in the console works fine.
So my question: Does anyone have a solution to this problem? Or perhaps a better/more appropriate way of going about it? It seems like it should be something simple.
Thanks for any help
For the moment, I got around this by simply setting the values of format and value_converted again in the controller before saving:
def create
#entry = current_user.entries.new(entry_params)
#entry.format = current_user.format
#entry.value_converted = entry_params[:value_converted]
if #entry.save
...
end
Though this is probably not the most elegant solution (I have no idea whether my implementation is thread-safe) it does seem to work.

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.

break down a complex search query in Rails 3

I have a controller which has a lot of options being sent to it via a form and I'm wondering how best to separate them out as they are not all being used simultaneously. Ie sometimes no, tags, sometimes no price specified. For prices I have a default price set so I can work around with it always being there, but the tags either need to be there, or not. etc.
#locations = Location.find(params[:id])
#location = #locations.places.active.where("cache_price BETWEEN ? AND ?",price_low,price_high).tagged_with([params[:tags]).order(params[:sort]).paginate :page => params[:page]
I haven't seen any good examples of this, but I'm sure it must happen often... any suggestions? Also, even will_paginate which gets tacked on last should be optional as the results either go to a list or to a google map, and the map needs no pagination.
the first thing to do when refactoring a complex search action is to use an anonymous scope.
Ie :
fruits = Fruit.scoped
fruits = fruits.where(:colour => 'red') if options[:red_only]
fruits = fruits.where(:size => 'big') if options[:big_only]
fruits = fruits.limit(10) if options[:only_first]
...
If the action controller still remains too big, you may use a class to handle the search. Moreover, by using a class with Rails 3 and ActiveModel you'll also be able to use validations if you want...
Take a look at one of my plugins : http://github.com/novagile/basic_active_model that allows you to easily create classes that may be used in forms.
Also take a look at http://github.com/novagile/scoped-search another plugin more specialized in creating search objects by using the scopes of a model.

Resources