ruby &:method syntax - ruby-on-rails

I ran into a bug where i left out a space like this
#entries = [#<Entry id: 3806, approved: false, submitted: false]
#entries.any?&:submitted? => true
#entries.any? &:submitted? => false
how does the space change the behavior. Entry.submitted? => false for all the elements in the array, the second line has the desired behavior.

The problem is that & is also a Unary Operator that has a precedence.
When you do this:
#entries.any?&:submitted?
You are actually doing is this:
(#entries.any?)&(:submitted?)
Which results in true. Since there are entries in #entries and the symbol :submitted? has a truth value of true.
When you do this:
#entries.any? &:submitted?
What you are actually doing is this:
#entries.any?(&:submitted?)
Which is what you really want.
The reason #pilcrow got his to work is that he/she was using the following class:
class Entry
def submitted?
true
end
end
To reproduce the result, use the following class:
class Entry
def submitted?
false
end
end
P.S: Same thing with #fl00r's example:
[1,2,nil].any?&:nil? and [1,2,nil].any? &:nil?
[1,2,nil].any? results in true and :nil? has a truth value of true, so the results are the same since [1,2,nil] also contains a nil value, but the calculations are different.

Related

How to properly update model using graphql-ruby?

I'm working on a side project to learn implementation of GraphQL into a Rails 6 app. To do this, I'm using the graphql-ruby gem.
I've got a resolve method to update a Medium model that looks like this:
module Mutations
module Media
class UpdateMedia < GraphQL::Schema::Mutation
include ::GraphqlAuthenticationConcerns
include ::GraphqlActiveModelConcerns
description 'Update Media'
argument :id, Integer, required: true
argument :title, String, required: false
argument :preview_url, String, required: false
argument :preview_image, String, required: false
argument :watched, Boolean, required: false
field :success, Boolean, null: false
field :errors, [Types::ActiveModelError], null: false
field :media, Types::MediumType, null: false
def resolve(id:, title:, release_date:, preview_url:, preview_image:, watched:)
authenticate_user!
media = Medium.find(id)
media_params = {
title: title,
preview_url: preview_url,
preview_image: preview_image,
watched: watched,
}
if media.update(media_params)
success_response(media)
else
failed_response(media)
end
end
private
def success_response(media)
{
success: true,
errors: [],
media: media
}
end
def failed_response(media)
{
success: false,
errors: errors(media)
}
end
end
end
end
If I set up the arguments in this way and I want to only update the watched field, I receive a 500 error stating missing keywords: :title, :release_date, :preview_url, :preview_image.
I saw this issue in the graphql-ruby repo from someone with the same problem, however they were told to set default values to nil, and when I tried this it of course sets every column for that model to nil.
I want to be able to change just the fields that are actually being passed as arguments, without affecting others. How do I allow for both a required parameter (id), as well as optional arguments?
Finally figured it out. By defining the method like this:
def resolve(id:, **args)
authenticate_user!
media = Medium.find(id)
if media.update(args)
success_response(media)
else
failed_response(media)
end
end
this keeps the id argument as required, and allows other params to pass through without setting the entire record to nil.
Ended up being more of a general Ruby question rather than specific to graphql-ruby.

How to write a graphQL mutation that allows for partial updates?

So I'm working on trying to learn GraphQL for ruby for a project.
I've almost got some parts of it up and running, but I'm having issues with other parts. There are plenty of tutorials out there that cover ultra-basics, but none of them seem to expand in the right directions.
I have a mutation to update my user. So far so good. I can look up the user by their ID, and update a single specific field. I can extend that to updating two fields.
What I cannot do, and this is looking insane, is generalize those fields -- at all. My user model will wind up with over 20 fields attached to it -- phone numbers, addresses, job title, etc etc.
When I create the mutation, I have to define the arguments that go into the resolve method. So far so good. I then define the fields the mutation can return. Again, so far so good.
Then I get to the actual resolve method.
The initial syntax isn't bad. def resolve(user_id:, name:, email:). Then you discover that despite setting required to false, you have to include all the values. You need to specify default values for the optional variables. So it becomes def resolve(user_id:, name: null, email: null) -- but that actually nulls out those values, you can't do partial updates. Worse yet, imagine having 20 fields you have to set this way. You can play games by trying to convert the arguments into a dictionary and rejecting null values -- but then you can't set properties to nil if they need to be nil again.
The solution: a double splat operator. Your syntax becomes def resolve(user_id:, **args). From what I can tell, it turns all remaining named arguments into a dictionary -- and I think unnamed arguments would become an array. Not sure how it would react with a mix of the two.
Full model becomes:
argument :user_id, ID, required: true#, loads: Types::UserType
argument :name, String, required: false
argument :email, String, required: false
field :user, Types::UserType, null: true
field :errors, Types::UserType, null: true
def resolve(user_id:, **args)
user = User.find(user_id)
if user.update(args)
{
user: user,
errors: []
}
else
{
user: nil,
errors: user.errors.full_messages
}
end
end
end

Rails 4 to 5 AR boolean deprecation

I have a model wish contains the bellow method called by before_validation :
def set_to_false
self.confirme ||= false
self.deny ||= false
self.favoris ||= false
self.code_valid ||= false
end
When I run my tests, I got the deprecation message
DEPRECATION WARNING: You attempted to assign a value which is not
explicitly true or false to a boolean column. Currently this value
casts to false. This will change to match Ruby's semantics, and will
cast to true in Rails 5. If you would like to maintain the current
behavior, you should explicitly handle the values you would like cast
to false. (called from cast_value at
./Ruby2.1.0/lib/ruby/gems/2.1.0/gems/activerecord-4.2.1/lib/active_record/type/boolean.rb:17)
I understand I have to cast but I couldn't find a simple and smart way to do it. Any help to remove this deprecation would be great.
Here's a simple booleanification trick that I use often, double negation:
before_validation :booleanify
def booleanify
self.confirm = !!confirm
self.deny = !!deny
...
end
In case you are not familiar with this trick, it'll convert all values to their boolean equivalents, according to ruby rules (nil and false become false, everything else becomes true)
'foo' # => "foo"
!'foo' # => false
!!'foo' # => true
!nil # => true
!!nil # => false

Mongoid queries not returning anything on new model fields

I have a Rails application where I am trying to iterate over each object in a Model class depending on whether the object has been archived or not.
class Model
include Mongoid::Document
include Mongoid::Timestamps
field :example_id, type: Integer
field :archived, type: Boolean, default: false
def archive_all
Model.all.where(archived: false).each do |m|
m.archive!
end
end
end
However, the where clause isn't returning anything. When I go into the console and enter these lines, here is what I get:
Model.where(example_id: 3).count #=> 23
Model.where(archived: false).count #=> 0
Model.all.map(&:archived) #=> [false, false, false, ...]
I have other where clauses throughout the application and they seem to work fine. If it makes any difference, the 'archived' field is one that I just recently added.
What is happening here? What am I doing wrong?
When you say:
Model.where(archived: false)
you're looking for documents in MongoDB the archived field is exactly false. If you just added your archived field then none of the documents in your database will have that field (and no, the :default doesn't matter) so there won't be any with archived: false. You're probably better off looking for documents where archived is not true:
Model.where(:archived.ne => true).each(&:archive!)
You might want to add a validation on archived to ensure that it is always true or false and that every document has that field.

Check if certain value exists in array of hash map

I have an array of hash map. It looks like this:
params = []
CSV.foreach(......) do
one_line_item = {}
one_line_item[:sku] = "Hello"
one_line_item[:value] = "20000"
params << one_line_item
end
I want to check if :sku is in this array of hash or not. I am doing it like this:
# Reading new line of csv in a hash and saving it in a temporary variable (Say Y)
params.each do |p|
if p[:sku] == Y[:sku]
next
end
end
I am iterating through the complete list for every value of sku, and thus time complexity is going for a toss [O(n^2)], need less to say it is useless.
Is there a way I can use include??
If I can get an array of values corresponding to the key :sku from the whole array in one shot, it would solve my problem. (I know I can maintain another array for these values but I want to avoid that)
One example of params
params = [{:sku=>"hello", :value=>"5000"}, {:sku=>"world", :value=>"6000"}, {:sku=>"Hi", :value=>"7000"}]
The any? and include? methods sound like what you need.
Example:
params.any? { |param| param.include?(:sku) }
This is an efficient way to do it, as it "short circuits", stopping as soon as a match is found.
So what you want is to collect a list of all SKUs. Are you looking to key sku => value?
Hash[*params.map { |p| [p[:sku], p[:value]] }.flatten]
That will give you a map of each sku to the value, which you can then do quick key lookups with sku_hash.key?(tester)
You may use rails_param gem for doing the same. I find it a very useful utility for validation request params in controller:
https://github.com/nicolasblanco/rails_param
# primitive datatype syntax
param! :integer_array, Array do |array,index|
array.param! index, Integer, required: true
end
# complex array
param! :books_array, Array, required: true do |b|
b.param! :title, String, blank: false
b.param! :author, Hash, required: true do |a|
a.param! :first_name, String
a.param! :last_name, String, required: true
end
b.param! :subjects, Array do |s,i|
s.param! i, String, blank: false
end
end

Resources