More concise way of writing this array inclusion / default fallback code? - ruby

I find that I've been doing this a fair enough number of times in my Rails controllers that I'm interested in finding a better way of writing it out (if possible). Essentially, I'm validating the input to a few options, and falling back on a default value if the input doesn't match any of the options.
valid_options = %w(most_active most_recent most_popular)
#my_param = valid_options.include?(params[:my_param]) ? params[:my_param] : 'most_recent'

If you use a hash instead of an array, it would be faster and cleaner. And, since your default is "most_recent", having "most_recent" in valid_options is redundant. You better remove it.
filter_options =
Hash.new("most_recent")
.merge("most_popular" => "most_popular", "most_active" => "most_active")
#my_param = filter_options[params[:my_param]]

I too would go the Hash route.
This could be imaginable:
Hash[valid_options.zip valid_options].fetch(params[:my_param], "most_recent")

A bit farfetched.
valid_options = %w(most_active most_recent most_popular)
(valid_options & [params[:my_param]]).first || 'most_recent'

How is the below:
valid_options = %w(most_active most_recent most_popular)
valid_options.detect(proc{'default_value'}){|i| i == params[:my_param] }
Another one:
valid_options = %w(most_active most_recent most_popular)
valid_options.dup.delete(params[:my_param]) { "default" }

Related

Alternative to logical OR, where false is treated as truthy?

So I have some code that gets values from a couple sources in a fixed priority order. It looks like this:
{ foo: true
bar: 42
}.each_pair do |attrib,default|
instance_variable_set :"##{attrib}",data[attrib] || template[attrib] || otherdata[attrib] || default
end
As you can probably tell, trying to override a true default with false won't work. How would I fix this without creating a horrible mess? (I do know how to fix it with a horrible mess, but I'm fairly certain there is something nice in the standard library for this exact situation, I just don't remember what it is called and can't think of a good search term)
If you're looking for the first non-nil value:
instance_variable_set :"##{attrib}", [ data[attrib], template[attrib], otherdata[attrib], default ].compact.first
Not the most efficient method, but gets the job done and works well if not exercised aggressively.
A slightly more forgiving version:
instance_variable_set :"##{attrib}", [ data[attrib], template[attrib], otherdata[attrib], default ].find { |v| !v.nil? }
This scans for the first non-nil value and returns that. It doesn't produce an intermediate array like compact does.
What you really want is a more forgiving method:
def instance_variable_set_from(attrib, *sources)
instance_variable_set(:"##{attrib}", sources.find { |v| !v.nil? }
end
Which is more self-explanatory when used:
instance_variable_set_from(attrib, data[attrib], template[attrib], otherdata[attrib], default)
Assuming that data, template and otherdata are all hashes, you could use fetch. It returns the value if it is contained in the hash, or the block's result otherwise:
value = data.fetch(attrib) { template.fetch(attrib) { otherdata.fetch(attrib, default) } }
instance_variable_set :"##{attrib}", value
or alternatively find the first hash containing the key and fetch its result, falling back to default:
value = [data, template, other_data].find { |h| h.key?(attrib) }.fetch(attrib, default)
Note that this would also allow nil values.

How to parse two elements from a list to make a new one

I have this input repeated in 1850 files:
[
{
"id"=>66939,
"login"=>"XXX",
"url"=>"https://website.com/XX/users/XXX"
},
...
{}
]
And I wanted to make a list in a way that by looking for the login I can retrieve the ID using a syntax like:
users_list[XXX]
This is my desired output:
{"XXX"=>"66570", "XXX"=>"66570", "XXX"=>"66570", "XXX"=>"66570", ... }
My code is:
i2 = 1
while i2 != users_list_raw.parsed.count
temp_user = users_list_raw.parsed[i2]
temp_user_login = temp_user['login']
temp_user_id = temp_user['id']
user = {
temp_user_login => temp_user_id
}
users_list << user
i2 += 1
end
My output is:
[{"XXX":66570},{"XXX":66569},{"XXX":66568},{"XXX":66567},{"XXX":66566}, ... {}]
but this is not what I want.
What's wrong with my code?
hash[key] = value to add an entry in a hash. So I guess in your case users_list[temp_user_login] = temp_user_id
But I'm unsure why you'd want to do that. I think you could look up the id of a user by having the login with a statement like:
login = XXX
user = users_list.select {|user| user["login"] == login}.first
id = user["id"]
and maybe put that in a function get_id(login) which takes the login as its parameter?
Also, you might want to look into databases if you're going to manipulate large amounts of data like this. ORMs (Object Relational Mappers) are available in Ruby such as Data Mapper and Active Record (which comes bundled with Rails), they allow you to "model" the data and create Ruby objects from data stored in a database, without writing SQL queries manually.
If your goal is to lookup users_list[XXX] then a Hash would work well. We can construct that quite simply:
users_list = users_list_raw.parsed.each.with_object({}) do |user, list|
list[user['login']] = user['id']
end
Any time you find yourself writing a while loop in Ruby, there might be a more idiomatic solution.
If you want to keep track of a mapping from keys to values, the best data structure is a hash. Be aware that assignment via the array operator will replace existing values in the hash.
login_to_id = {}
Dir.glob("*.txt") { |filename| # Use Dir.glob to find all files that you want to process
data = eval(File.read(filename)) # Your data seems to be Ruby encoded hash/arrays. Eval is unsafe, I hope you know what you are doing.
data.each { |hash|
login_to_id[hash["login"]] = hash["id"]
}
}
puts login_to_id["XXX"] # => 66939

How to set default value for Dry::Validation.Params scheme?

I have next scheme
Dry::Validation.Params do
optional(:per_page).filled(:int?, lteq?: 1000)
optional(:page).filled(:int?)
end
If I pass empty hash for validation I get empty output but I want to set default values for my data.
I tried Dry::Types.default but it does not add default values in output. That's what I tried.
Dry::Validation.Params do
optional(:per_page).filled(Dry::Types['strict.integer'].default(10), lteq?: 1000)
optional(:page).filled(:int?)
end
Is it possible to do what I want?
The Dry::Validation has not this purpose.
I recommend you to use dry-initializer on your params before pass it to the validation.
You can do something like this:
optional(:per_page).filled(Types::Integer.constructor { _1 || 10 })
Or define your own fallback strategy as here https://github.com/dry-rb/dry-types/pull/410
optional(:per_page).filled(Types::Integer.constructor { |input, type| type.(input) { 10 } })

OR operators and Ruby where clause

Probably really easy but im having trouble finding documentation online about this
I have two activerecord queries in Ruby that i want to join together via an OR operator
#pro = Project.where(:manager_user_id => current_user.id )
#proa = Project.where(:account_manager => current_user.id)
im new to ruby but tried this myself using ||
#pro = Project.where(:manager_user_id => current_user.id || :account_manager => current_user.id)
this didnt work, So 1. id like to know how to actually do this in Ruby and 2. if that person can also give me a heads up on the boolean syntax in a ruby statement like this altogether.
e.g. AND,OR,XOR...
You can't use the Hash syntax in this case.
Project.where("manager_user_id = ? OR account_manager = ?", current_user.id, current_user.id)
You should take a look at the API documentation and follow conventions, too. In this case for the code that you might send to the where method.
This should work:
#projects = Project.where("manager_user_id = '#{current_user.id}' or account_manager_id = '#{current_user.id}'")
This should be safe since I'm assuming current_user's id value comes from your own app and not from an external source such as form submissions. If you are using form submitted data that you intent to use in your queries you should use placeholders so that Rails creates properly escaped SQL.
# with placeholders
#projects = Project.where(["manager_user_id = ? or account_manager_id = ?", some_value_from_form1, some_value_from_form_2])
When you pass multiple parameters to the where method (the example with placeholders), the first parameter will be treated by Rails as a template for the SQL. The remaining elements in the array will be replaced at runtime by the number of placeholders (?) you use in the first element, which is the template.
Metawhere can do OR operations, plus a lot of other nifty things.

ruby one-liner for this possible?

Any chance the 2nd and 3rd lines can be combined in an one-liner and hopefully save one valuable?
def self.date_format
record = find_by_key('strftime')
record ? record.value : "%Y-%b-%d'
end
the above function in a Config model try to fetch a database record by a key, return a default if not found in database.
Even better if can be written in named scope. Thanks
As requested.
Nobody yet has mentioned try, which is perfect for this situation:
value = find_by_key('strftime').try(:value) || "%Y-%b-%d"
You could use:
(find_by_key('strftime').value rescue nil) || "%Y-%b-%d"
though using exceptions is not very efficient.
Does
value = find_by_key('strftime') || "%Y-%b-%d"
work for you?
Do you need to assign a "value" variable at all? If not...
def self.date_format
find_by_key('strftime') || "%Y-%b-%d"
end

Resources