This might not be as much neo4j as it is rails. But maybe I'm wrong
I'm looking for suggestions on handling a situation when parameters are specific values. For example. In my query
#friends_events = current_user.friends.events(:e, :rel).where("rel.admin = {admin_p} AND e.size_max < {size_p}", uuid: #event_id, primary_category: params[:primary_category] ).params(admin_p: true, size_p: params[:group_max].to_i)
The event has a size_max property which can be an integer or it can be any. Right now I have any as a blank value. Basically if they choose any, I need to ignore that parameter all together in the query (or handle it in a similar fashion). A way to cheat it is to handle the situation outside and if any is selected and the value is blank, I manually set it to a really high number which won't be hit.
Not sure how to do this either inside or outside the query without an odd hacky way right now. Suggestions?
Update
I have my query as you suggested. and the methods to deal with the 'validation'
#friends_events = current_user.friends.events(:e, :rel).where("rel.admin = {admin_p} #{size_string}", uuid: #event_id, primary_category: params[:primary_category] ).params(admin_p: true, size_p: size_param)
And I changed the size_param to blank which it doesn't like. I wanted to be able to handle both cases. e.g. when you first hit the page params is empty, and when you submit, it's blank. Nil will work with scenario 1 and not scenario 2. Do I need a || case here?
def size_string
'AND e.size_max < {size_p}' if params[:group_max]
end
def size_param
#has not taken in blank scenario
params[:group_max].blank? ? false : params[:group_max].to_i
end
and in my view I have
<% if !#friends_events.blank? %>
My error is
Don't know how to compare that. Left: 0 (Long); Right: false (Boolean) from the line above in my view. Changing .blank? to .nil? allows the filter to go through (though incorrectly)
The ways of handling it that you identified seem like the best options. It may be a little more work to evaluate it before hitting the database but you'll get a performance boost by omitting that property entirely if the user wants any. I have been told that filtering using > or <, does not use indexes, so it may be slow if you have enough records. Start with something like this:
def where_this_happens
#friends_events = current_user.friends.events(:e, :rel)
.where("rel.admin = {admin_p} #{size_string}", uuid: #event_id, primary_category: params[:primary_category] )
.params(admin_p: true, size_p: size_param)
end
def size_string
'AND e.size_max < {size_p}' unless params[:group_max].blank?
end
def size_param
params[:group_max].nil? ? false : params[:group_max].to_i
end
There's no harm in setting the size_p param and not using it but I'm pretty sure it will bark at you if you feed it nil.
Related
I am trying to normalize some data in an ETL process because the data we get is not consistent.
Annoying but i am here to learn.
currently we do something like:
received = datum[:quantity_received] || datum[:received_cases] || datum[:received_quantity]
Curious if there is a more ruby/rails way of doing this?
considering:
received = datum.values_at(:quantity_received,:received_cases,:received_quantity).compact.first
I don't think there is a much better solution. I'd try to define some helper methods (I'm not a long lines' supporter)
def value_you_need(datum)
datum.values_at(*keys_of_interest).find(&:itself)
end
def keys_of_interest
%i(quantity_received received_cases received_quantity)
end
received = value_you_need(datum)
object#itself method is present from ruby 2.2.0. Otherwise, go for compact.first.
Note a detail: if false is one of the values you care about the solution with compact.first is the only one correct of our three. But I took for granted you don't or the first option would be wrong.
I have a simple ActiveRecord query along the lines of this:
similar_changes = Notification.where(change_owner: 'foo1', change_target: 'foo2', change_cancelled: false)
Each notification object has a field change_type and I have another function that checks one Notification's change_type with one other Notification for inverse changes (changes that undo each other in the context of my application).
I need to take this Notification's change_type and compare it against all others in the array. I have to reference the objects like so: similar_changes[0]['change_type'] where the first index is each ActiveRecord in the array and the second is the dictionary that specifies which property in the Notification object.
I have a feeling I could do this manually with two nested loops and if statements, but I also know Ruby and I feel like this is something that it should have built in.
Am I wrong, or is there a better way to do this?
Here is the code (note all this code isn't quite finished so bear with me if it's not perfect):
def self.group_similar_changes(owner, target, change_type)
# long query where it selects all rows where change_owner and change_target
# are the same as original
# also where cancelled is false
# determine if cancelled (yaml)
# if cancelled (do nothing)
similar_changes = Notification.where(
change_owner: owner,
change_target: target,
change_cancelled: false
)
similar_changes.each do |change|
cancel_inverse_change(change, change.change_type)
if change.cancelled?
similar_changes.delete(change)
end
end
end
end
def cancel_inverse_change(change, change_type)
if change.inverse?(change_type)
change.cancel
end
end
def inverse?(possible_inverse_change)
is_inverse = false
change_types = YAML.load_file(File.join(NotificationManager::Engine.root, 'config/change_types.yaml'))
if self.change_type == change_types[possible_inverse_change]['inverse']
is_inverse = true
end
return is_inverse
end
Yes, your loop over similar_changes can be improved.
It's confusing to modify the array you're looping over. I don't even know if it's reliable, because I never do it!
It's also not idiomatic Ruby to rely on the return value of each. each is normally used to do something to the elements of an Enumerable that already exists, so using its return value seems strange.
I'd write it as
similar_changes.reject do |change|
cancel_inverse_change(change, change.change)
change.cancelled?
end
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.
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.
In my application, these "planners" (essentially, article ideas) follow predetermined templates, written in Markdown, with some specific syntax here:
Please write your answer in the following textbox: [...]
Please write your answer in the following textarea:
...So here, on line, you should write one thing.
...Here, on line 2, you should write another.
...
...
...
Essentially, [...] is a text input, and a group of lines starting with ... are a textarea. That's not really the issue - it's just to explain what part of this code is doing.
On actions new and edit, the standard planner form is displayed, with the correct fields based on the template (for new) or current planner body (for edit). On save, the template's fields are filled in with params[:fields], and the resulting Markdown is saved as the planner's body. The code, I'd hope, is now possible to follow, knowing this context. Only relevant controller code is provided, and it uses make_resourceful.
class Staff::PlannersController < StaffController
make_resourceful do
actions :all
before :create do
find_planner_format
if #planner_format
current_object.body = fields_in_template #planner_format.body
else
flash[:error] = 'Planner format not found!'
redirect_to staff_planners_path
end
current_object.user = #current_user
end
before :update do
current_object.body = fields_in_template(current_object.body)
end
end
private
def fields_in_template(template)
fields = params[:fields] || {}
if fields[:inline]
template.gsub! /\[\.\.\..*\]/ do
"[...#{fields[:inline].shift}]"
end
end
if fields[:block]
template.gsub! /^\.{3}.*(\n\.{3}.*)*$/ do
fields[:block].shift.split("\n").collect { |line|
"...#{line}"
}.join("\n")
end
end
current_object.body = template
end
end
And now, the mystery: in the update action, changes to the body are not saved. After debugging, I've determined that the issue does not lie only in current_object.save, since the following before :update code does what you would expect:
before :update do
current_object.body = 'test string'
end
In fact, even this gets the expected result:
before :update do
current_object.body = fields_in_template(current_object.body) + 'a'
end
So now, the question: why is Rails so insistent that it not save the result of the function - and even then, only when it comes from update? More debugging showed that the object attribute is set, and even claims to save successfully, but reloading the object after save reverts the changes.
At first it looked like the resulting string was just a "poisoned" variable of sorts, and that rebuilding the string by appending "a" removed that strange state. However, the following code, which ought to add an "a" and remove it again, also failed to save.
before :update do
new_body = fields_in_template(current_object.body) + 'a'
new_body.slice! -1
current_object.body = new_body
end
This is just bizarre to me. What am I doing wrong here, and what can I possibly do to debug further? (Or if you happen to instantly see my mistake, that'd be nice, too...)
EDIT: After checking SQL logs (not sure why I didn't think to do this earlier), it would seem that Rails doesn't seem to acknowledge the new body attribute as actually being different, even though checking the string in the debugger confirms that it is. As such, Rails doesn't even run an UPDATE query unless something else is modified, in which case body is not included.
Got it! Sometimes it just helps to state the question out loud...
The deal is, I had forgotten that, when passing current_object.body to fields_in_template, it was being passed by reference. As such, all gsub! methods were running directly on current_object.body, so Rails acknowledged no real "changes" by the time I set body to what had just been set.
The solution:
def fields_in_template(template)
template = template.dup
# ...
end
Thanks for letting me talk to myself, and mission accomplished!
I'm not a Ruby programmer but does adding an 'a' convert the type of the variable to string? Maybe your variable is of the wrong type without adding 'a'.