What is syntactically wrong with this Ruby case statement? - ruby-on-rails

For some reason, the when #orgs is not working :
#orgs = Organization.all.select{|a|a.active}.count
case collection.size
when 0; "No #{entry_name.pluralize} found"
when #orgs; "#{#orgs} Businesses Returned!"
else; "#{collection.total_entries} of #{#orgs} Businesses Returned!"
end
Is this syntactically accurate? It will always return the last else statement. It never catches on the second when statement, even if #orgs == #orgs.
The actual number of #orgs = 1211.
So if I make the when statement when 1211; , it still doesn't catch. Is there a syntactical mistake here?

Did you try the following syntax ?
case collection.size
when 0 then "No #{entry_name.pluralize} found"
when #orgs then "#{#orgs} Businesses Returned!"
else "#{collection.total_entries} of #{#orgs} Businesses Returned!"
end

Your syntax isn't the issue. The logic in your conditions, I think, is all messed up.
So, #orgs does not = 1211, #orgs is a collection whose size is 1211. Big difference.
This case statement will always fail because the case is collection.size, when it should probably be #orgs.size
Let's say we changed it to be:
case #orgs.size
...
Then:
Your first when statement would start working when #orgs.size == 0
Your second when statement would still fail, because #orgs.size != #orgs - and it never will. #orgs refers to the collection of objects, while #orgs.size refers to the size of that collection. Even when #orgs == nil it will still fail because #orgs.size will throw a error because you're calling the size method on a nil object, which isn't allowed.
Your else statement is always the one called because of the problems listed above, and because the way you've written it prevent any of the other cases from ever being true.
However, that's probably still not enough. It looks to me like you're trying to return singularized or pluralized text depending on how many you have. Here's the code you probably intended to write, without the semi-colon usage:
#active_orgs = Organization.all.select{|a| a.active}
#singular_name = Organization.class.name
#plural_name = #singular_form.pluralize
case #active_orgs.size
when 1
"1 active #{#singular_name} of a total #{Organization.count} #{#plural_name}!"
else
"#{#active_orgs.count} active #{#singular_name} of a total #{Organization.count} #{#plural_name}!"
end
This will pluralize even when the count is 0, so it will say 0 active Organizations of a total 125 Organizations!
... or when the value is 1 it will say 1 active Organization of a total 125 Organziations!
If you don't want Organization capitalized, you can add a call to .downcase to take care of that.

Related

Glua error "attempt to call method 'GetPlayerVisible' (a nil value)"

I personally don't know how a built in function can be indexed as "nil" butthis error appeared and it haulted my nextbot's movement. heres my code that is causing this
if (!self:GetPlayerVisible() and chasing_timer > chasing_time) then
self.stopchasing = true
self.enraged = false
print("Chase stopped")
print("Increasing escaped chases count, new:", self.escapedchases)
self.escapedchases = self.escapedchases + 1
end
I tried replacing the "!" with "not" but it did nothing.
I don't know what self is but that table does not contain an element GetPlayerVisible. Hece you may not call it. Calling nil values doesn't make sense.
Ask yourself why you think this function exists. Typical reasons are typos, indexing the wrong table or not having implemented a function yet.
To avoid this error you basically have two options. Make sure you call something that exists, or don't call it.

Ruby - Next if vs Next unless

What is the difference between next if vs next unless.
My understanding of next if was that next only if variable == value but in the following code, the second next statement isn't working. next if datastore.id == 5 it is bypassing this and proceeding even if the datastore.id == 2 or datastore.id == 3 etc.,
$evm.vmdb(:ManageIQ_Providers_Vmware_InfraManager_Storage).all.each do |datastore|
next if datastore.ems_id == provider.id.to_s
next if datastore.id == 5
dialog_hash[datastore[:id]] = "#{datastore.name} on #{datastore.ext_management_system.name}"
end
But if I change the next if to next unless then it works fine.
$evm.vmdb(:ManageIQ_Providers_Vmware_InfraManager_Storage).all.each do |datastore|
next if datastore.ems_id == provider.id.to_s # Next if works here
next unless datastore.id == 5 # Next unless works here instead of next if
dialog_hash[datastore[:id]] = "#{datastore.name} on #{datastore.ext_management_system.name}"
end
I'll quickly explain next and the difference between if and unless in ruby, and proceed to suggest some things you could try to get your code to work.
if
Code would execute if the condition is true.
unless
Code would execute if the condition is false. It would work the same as:
if !(expression) { ... }
next
The program would skip the rest of the code inside the loop block, and skip to the next iteration.
In your case, if there is a next called in the first line, then the rest of the code wouldn't run.
$evm.vmdb(:ManageIQ_Providers_Vmware_InfraManager_Storage).all.each do |datastore|
next if datastore.ems_id == provider.id.to_s # if this condition is true, don't run rest of the code in this loop, and go to the next datastore
next if datastore.id == 5
dialog_hash[datastore[:id]] = "#{datastore.name} on #{datastore.ext_management_system.name}"
end
Suggestion
It's unclear from your question what you mean by if doesn't work but unless does.
From the first code snippet in the question, the last line of the code. i.e,
dialog_hash[datastore[:id]] = "#{datastore.name} on #{datastore.ext_management_system.name}"
would only run if both of the conditions above are false.
You can check where the data is unexpected, or if your initial conditions are wrong, by debugging the datastore with either a debugger or some puts statements inside the loop.
All the best.
The difference is that
next if condition calls next when the condition is true, but
next unless condition calls next when the condition is false.
When calling next with a condition like datastore.id == 5 doesn't work as expected, then the problem is not the usage of if or unless because they work the opposite way.
Instead, you need to debug why you expect datastore.id == 5 to be true and why it is not. Obviously, the condition datastore.id == 5 can only be true when datastore.id returns the integer 5. If it returns false then datastore.id might return a string with the digit "5".
I suggest adding debug output to your code to dig deeper, like this:
p datastore.id
p datastore.id.class
Additionally to answers, most popular Ruby style guide recommends to use positive instead of negative conditions
# bad
do_something if !some_condition
# good
do_something unless some_condition
Depending on this, you can choose if or unless

cyclomatic complexity is too high rubocop for method

This is code I have using in my project.
Please suggest some optimizations (I have refactored this code a lot but I can't think of any progress further to optimize it )
def convert_uuid_to_emails(user_payload)
return unless (user_payload[:target] == 'ticket' or user_payload[:target] == 'change')
action_data = user_payload[:actions]
action_data.each do |data|
is_add_project = data[:name] == 'add_fr_project'
is_task = data[:name] == 'add_fr_task'
next unless (is_add_project or is_task)
has_reporter_uuid = is_task && Va::Action::USER_TYPES.exclude?(data[:reporter_uuid])
user_uuids = data[:user_uuids] || []
user_uuids << data[:owner_uuid] if Va::Action::USER_TYPES.exclude?(data[:owner_uuid])
user_uuids << data[:reporter_uuid] if has_reporter_uuid
users_data = current_account.authorizations.includes(:user).where(uid: user_uuids).each_with_object({}) { |a, o| o[a.uid] = {uuid: a.uid, user_id: a.user.id, user_name: a.user.name} }
if Va::Action::USER_TYPES.include? data[:owner_uuid]
data['owner_details'] = {}
else
data['owner_details'] = users_data[data[:owner_uuid]]
users_data.delete(data[:owner_uuid])
end
data['reporter_details'] = has_reporter_uuid ? users_data[data[:reporter_uuid]] : {}
data['user_details'] = users_data.values
end
end
Note that Rubocop is complaining that your code is too hard to understand, not that it won't work correctly. The method is called convert_uuid_to_emails, but it doesn't just do that:
validates payload is one of two types
filters the items in the payload by two other types
determines the presence of various user roles in the input
shove all the found user UUIDs into an array
convert the UUIDs into users by looking them up
find them again in the array to enrich the various types of user details in the payload
This comes down to a big violation of the SRP (single responsibility principle), not to mention that it is a method that might surprise the caller with its unexpected list of side effects.
Obviously, all of these steps still need to be done, just not all in the same method.
Consider breaking these steps out into separate methods that you can compose into an enrich_payload_data method that works at a higher level of abstraction, keeping the details of how each part works local to each method. I would probably create a method that takes a UUID and converts it to a user, which can be called each time you need to look up a UUID to get the user details, as this doesn't appear to be role-specific.
The booleans is_task, is_add_project, and has_reporter_uuid are just intermediate variables that clutter up the code, and you probably won't need them if you break it down into smaller methods.

How do I iterate on a collection when I don't know what the upper limit of iterations is?

I have an API that I am pulling data from, and I want to collect all the tags from this API...but I don't know the number of tags in advance, and the API throttles access via the max number of results returned in any 1 call (100). It has an unlimited number of pages though.
So a call may look like this: Tag.update_tags(100, 5) where 100 is the max number of objects returned in 1 call and 5 is the page to begin (i.e. if you assume that the tags are stored sequentially, what this is saying is return the tag records with IDs in the range of 401 - 500.
The issue is, I don't want to manually have to enter 5 (i.e. I don't know what the upper limit is). There is no way for me to ping the total number of tags (if there were, I would simply divide it and put this call in a loop up to that number).
All I do know is that once it reaches a page that doesn't have any results, it will return an empty array [].
So, how do I loop through all the tags and stop when the result returned is an empty array (which would be the final result returned and therefore not evaluated)?
What does that loop look like?
Use an unconditional loop with a break statement when the result returns the empty array.
i = 1
loop do
result = call_to_api(i)
do_something_with(result)
i += 1
break if result.empty?
end
Of course in a production scenario you want something a little more robust, including exception handlers, some progress log reporting, and some kind of concrete iteration limit to ensure that the loop does not become infinite.
Update
Here's an example using a class to wrap up the logic.
class Api
DEFAULT_OPTIONS = {:start_position => 1, :max_iterations => 1000}
def initialize(base_uri, config)
#config = DEFAULT_OPTIONS.merge(config)
#position = config[:start_position]
#results_count = 0
end
def each(&block)
advance(&block) while can_advance?
log("Processed #{#results_count} results")
end
def advance(&block)
yield result
#results_count += result.count
#position += 1
#current_result = nil
end
def result
#current_result ||= begin
response = Net::HTTP.get_response(current_uri)
JSON.decode(response.body)
rescue
# provide some exception handling/logging
end
end
def can_advance?
#position < (#config[:start_position] + #config[:max_iterations]) && result.any?
end
def current_uri
Uri.parse("#{#base_uri}?page=#{#position}")
end
end
api = Api.new('http://somesite.com/api/v1/resource')
api.each do |result|
do_something_with(result)
end
There's also an angle with this to allow for concurrency by setting the start and iteration count for each thread which would definetly speed this up with the concurrent http requests.
Hmmm. You can get 100 items at a time, and start at a particular page. How to implement the iteration depends on what you want to do. Let's suppose that you want to collect all the unique tags. Establish a map (for example, a HashMap), then retrieve one page at a time and process it. When you hit a page that's empty, you're done.
// Implements a map and methods to update it
MyHashMap uniqueTags;
// Stores a page of tags
Page page;
Do
// get a page of tags
page = readTags();
if (page != null) {
uniqueTags.getUniqueTags(page);
} else {
break;
}
until (page == null);

find_or_create and race-condition in rails, theory and production

Hi I've this piece of code
class Place < ActiveRecord::Base
def self.find_or_create_by_latlon(lat, lon)
place_id = call_external_webapi
result = Place.where(:place_id => place_id).limit(1)
result = Place.create(:place_id => place_id, ... ) if result.empty? #!
result
end
end
Then I'd like to do in another model or controller
p = Post.new
p.place = Place.find_or_create_by_latlon(XXXXX, YYYYY) # race-condition
p.save
But Place.find_or_create_by_latlon takes too much time to get the data if the action executed is create and sometimes in production p.place is nil.
How can I force to wait for the response before execute p.save ?
thanks for your advices
You're right that this is a race condition and it can often be triggered by people who double click submit buttons on forms. What you might do is loop back if you encounter an error.
result = Place.find_by_place_id(...) ||
Place.create(...) ||
Place.find_by_place_id(...)
There are more elegant ways of doing this, but the basic method is here.
I had to deal with a similar problem. In our backend a user is is created from a token if the user doesn't exist. AFTER a user record is already created, a slow API call gets sent to update the users information.
def self.find_or_create_by_facebook_id(facebook_id)
User.find_by_facebook_id(facebook_id) || User.create(facebook_id: facebook_id)
rescue ActiveRecord::RecordNotUnique => e
User.find_by_facebook_id(facebook_id)
end
def self.find_by_token(token)
facebook_id = get_facebook_id_from_token(token)
user = User.find_or_create_by_facebook_id(facebook_id)
if user.unregistered?
user.update_profile_from_facebook
user.mark_as_registered
user.save
end
return user
end
The step of the strategy is to first remove the slow API call (in my case update_profile_from_facebook) from the create method. Because the operation takes so long, you are significantly increasing the chance of duplicate insert operations when you include the operation as part of the call to create.
The second step is to add a unique constraint to your database column to ensure duplicates aren't created.
The final step is to create a function that will catch the RecordNotUnique exception in the rare case where duplicate insert operations are sent to the database.
This may not be the most elegant solution but it worked for us.
I hit this inside a sidekick job that retries and gets the error repeatedly and eventually clears itself. The best explanation I've found is on a blog post here. The gist is that postgres keeps an internally stored value for incrementing the primary key that gets messed up somehow. This rings true for me because I'm setting the primary key and not just using an incremented value so that's likely how this cropped up. The solution from the comments in the link above appears to be to call ActiveRecord::Base.connection.reset_pk_sequence!(table_name) This cleared up the issue for me.
begin
result = Place.where(:place_id => place_id).limit(1)
result = Place.create(:place_id => place_id, ... ) if result.empty? #!
rescue ActiveRecord::StatementInvalid => error
#save_retry_count = (#save_retry_count || 1)
ActiveRecord::Base.connection.reset_pk_sequence!(:place)
retry if( (#save_retry_count -= 1) >= 0 )
raise error
end

Resources