I'm trying to use union types with is_a? for flow control, but I'm still getting sorbet errors. I've tried casting as well, and I'm still running into the same error, which is:
Method to_hash does not exist on T::Array[T.untyped] component of T.any(T::Array[T.untyped], T::Hash[Symbol, T.untyped]) https://srb.help/7003
I have the following struct:
class PostProcessingMethod < T::Struct
prop :method_name, Symbol
prop :args, T.any(Array, T::Hash[Symbol, T.untyped]), default: []
prop :changed_fields, T::Array[String], default: []
prop :all, T::Boolean, default: false
prop :force, T::Boolean, default: false
end
and I'm using it in a method that (currently) looks like this:
sig { params(post_processing_methods: T::Array[Documents::PostProcessingMethod]).void }
def call(post_processing_methods)
post_processing_methods.each do |post_processing_method|
next unless should_call_method?(post_processing_method)
if #object.respond_to?(post_processing_method.method_name)
if post_processing_method.args.is_a?(Array)
#object.send(post_processing_method.method_name, *post_processing_method.args)
elsif post_processing_method.args.is_a?(Hash)
#object.send(post_processing_method.method_name, **post_processing_method.args)
end
end
end
end
I've tried incorporating T.cast to ensure that sorbet knows it's a Hash in the elsif, but that doesn't seem to have made a difference.
My expectation is that is_a? should allow sorbet to know that post_processing_method is a Hash in the elseif. But if that's not the case, the T.cast should certainly handle this.
In the code posted, each .args call is treated like a new variable. If you capture the returning value in a local variable, the flow sensitivity will work.
See an example on sorbet.run
I have a JSON column in my model that by default is an empty hash.
I want to check if specific keys are present and if not to merge the empty hash with a default hash with the keys.
In my model I am checking if a utility (water, gas, or electric is present) and if not then insert this default hash:
def default_config
{:config => {"features" => {"utilities" => {"water" => true, "gas" => true, "electric" => true}}}}
end
this is how I'm checking for whether a utility key is present:
def water
has_water? || parent.has_water?
end
which in turn calls these methods (all in my model):
def utility(util)
self[:config].try(:fetch, "features", nil).try(:fetch, "utilities", nil).try(:fetch, "#{util}", nil)
end
def has_water?
utility("water") == true
end
This is in order to be able to configure the JSON column whether or not the keys already present, which I'm attempting here:
def set_water(boolean)
new_val = cleaned_boolean(boolean)
water ? nil : self[:config].deep_merge!(default_config)
self[:config]["features"]["utilities"]["water"] = new_val
end
When I test this I'm getting
undefined method `[]=' for nil:NilClass
error when trying to set a utility value indicating that my default_config is not being merged into the existing empty hash.
reverse_merge! is the usual way to set defaults for a Hash in rails.
self[:config].reverse_merge!(default_config)
this is essentially equal to:
default_config.merge!(self[:config])
Leaving everything in self[:config] untouched and just merging in the missing key value pairs from default_config.
Also this ternary expression:
water ? nil : self[:config].deep_merge!(default_config)
is more idiomatically written as (using Hash#reverse_merge!)
self[:config].reverse_merge!(default_config) unless water
and since water returns a boolean value it is generally written as a question e.g. water? (like in has_water?) Not sure if the water method is used frequently but I would refactor as
def has_water?(include_parent=false)
utility("water") == true || (include_parent && parent.has_water?)
end
Then call as:
self[:config].reverse_merge!(default_config) unless has_water?(true)
I'm Using Ruby 1.8.7-p374 and Rails 2.3.18. (Yeah, I know, we're working on it.)
I'm considering dynamically passing a named scope to be used as a condition for an ActiveRecord. Is it possible to check to see if a passed string is a valid named scope for the record?
For Example:
If I have a named scope called :red defined for Car
named_scope :red, :condition => ["color = ?", "red"]
Then is there some function where I could do
Car.some_function("red") # returns true
Car.some_function("blue") # returns false
Thanks in advanced.
You can use .respond_to?(:method) (documentation here)
In your case :
Car.respond_to?(:red) # => true
Car.respond_to?(:blue) # => false
But you said:
I'm considering dynamically passing a named scope to be used as a condition for an ActiveRecord
I hope you will not use something like this:
# url
/cars?filter=red
# controller
def index
#cars = Car.send(params[:filter]) if params[:filter].present? && Car.respond_to?(params[:filter])
#cars ||= Car.find(:all)
Guess what woud happen if I use this URL?
/cars?filter=destroy_all
The Car model responds to the method .destroy_all, so Ruby calls it on the Car model. BOOM, all cars are destroyed!
Klass.scopes will return a hash of all scopes for that class. You can see if it's in there - the names are stored as symbols. eg
if Car.scopes[:red]
...
This will return the scope itself (truthy) or nil (falsy), which is fine for passing/failing an if test. If you literally want either true or false back then you can do !! on it to convert it to a boolean.
a_bool = !!Car.scopes[:red]
In Grails, if I define a locale, and put a date on specific format on i18n file, like (dd/mm/AAAA), if call one request like:
http://myapp/myaction?object.date=10/12/2013
When I get print: params.date, it comes to me a date object.
How can I do the same on rails?
Normally the Rails handles this for you. For instance, the form helper datetime_select works in conjunction with some activerecord magic
to ensure ensure time/date types survive the round-trip. There are various alternatives to the standard date-pickers.
If this doesn't work for you e.g. rails isn't generating the forms, there are (at least) a couple of options.
One option, slightly evi, is to monkey-patch HashWithIndifferentAccess (used by request params) to do type conversions based on the key name. It could look something like:
module AddTypedKeys
def [](key)
key?(key) ? super : find_candidate(key.to_s)
end
private
# look for key with a type extension
def find_candidate(key)
keys.each do |k|
name, type = k.split('.', 2)
return typify_param(self[k], type) if name == key
end
nil
end
def typify_param(value, type)
case type
when 'date'
value.to_date rescue nil
else
value
end
end
end
HashWithIndifferentAccess.send(:include, AddTypedKeys)
This will extend params[] in the way you describe. To use it within rais, you can drop it into an initialiser, eg confg/initializers/typed_params.rb
To see it working, you can test with
params = HashWithIndifferentAccess.new({'a' => 'hello', 'b.date' => '10/1/2013', 'c.date' => 'bob'})
puts params['b.date'] # returns string
puts params['b'] # returns timestamp
puts params['a'] # returns string
puts params['c'] # nil (invalid date parsed)
However... I'm not sure it's worth the effort, and it will likely not work with Rails 4 / StrongParameters.
A better solution would be using virtual attributes in your models. See this SO post for a really good example using chronic.
I'm submitting a parameter show_all with the value true. This value isn't associated with a model.
My controller is assigning this parameter to an instance variable:
#show_all = params[:show_all]
However, #show_all.is_a? String, and if #show_all == true always fails.
What values does Rails parse as booleans? How can I explicitly specify that my parameter is a boolean, and not a string?
UPDATE: Rails 5:
ActiveRecord::Type::Boolean.new.deserialize('0')
UPDATE: Rails 4.2 has public API for this:
ActiveRecord::Type::Boolean.new.type_cast_from_user("0") # false
PREVIOUS ANSWER:
ActiveRecord maintains a list of representations for true/false in https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/column.rb
2.0.0-p247 :005 > ActiveRecord::ConnectionAdapters::Column.value_to_boolean("ON")
2.0.0-p247 :006 > ActiveRecord::ConnectionAdapters::Column.value_to_boolean("F")
This is not part of Rails' public API, so I wrapped it into a helper method:
class ApplicationController < ActionController::Base
private
def parse_boolean(value)
ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
end
and added a basic test:
class ApplicationControllerTest < ActionController::TestCase
test "parses boolean params" do
refute ApplicationController.new.send(:parse_boolean, "OFF")
assert ApplicationController.new.send(:parse_boolean, "T")
end
end
I wanted to comment on zetetic answer but as I can't do that yet I'll post this as an answer.
If you use
#show_all = params[:show_all] == "1"
then you can drop ? true : false because params[:show_all] == "1" statement itself will evaluate to true or false and thus ternary operator is not needed.
This question is rather old, but since I came across this issue a couple of times, and didn't like any of the solutions proposed, I hacked something myself which allows to use multiple strings for true such as 'yes', 'on', 't' and the opposite for false.
Monkey patch the class String, and add a method to convert them to boolean, and put this file in /config/initializers as suggested here: Monkey Patching in Rails 3
class String
def to_bool
return true if ['true', '1', 'yes', 'on', 't'].include? self
return false if ['false', '0', 'no', 'off', 'f'].include? self
return nil
end
end
Notice that if the value is none of the valid ones either for true or false, then it returns nil. It's not the same to search for ?paid=false (return all records not paid) than ?paid= (I don't specify if it has to be paid or not -- so discard this).
Then, following this example, the logic in your controller would look like this:
Something.where(:paid => params[:paid].to_bool) unless params[:paid].try(:to_bool).nil?
It's pretty neat, and helps to keep controllers/models clean.
#show_all = params[:show_all] == "1" ? true : false
This should work nicely if you're passing the value in from a checkbox -- a missing key in a hash generates nil, which evaluates to false in a conditional.
EDIT
As pointed out here, the ternary operator is not necessary, so this can just be:
#show_all = params[:show_all] == "1"
You could change your equality statement to:
#show_all == "true"
If you want it to be a boolean you could create a method on the string class to convert a string to a boolean.
I think the simplest solution is to test "boolean" parameters against their String representation.
#show_all = params[:show_all]
if #show_all.to_s == "true"
# do stuff
end
Regardless of whether Rails delivers the parameter as the String "true" or "false" or an actual TrueClass or FalseClass, this test will always work.
You could just do
#show_all = params[:show_all].downcase == 'true'
It's worth noting that if you're passing down a value to an ActiveModel in Rails > 5.2, the simpler solution is to use attribute,
class Model
include ActiveModel::Attributes
attribute :show_all, :boolean
end
Model.new(show_all: '0').show_all # => false
As can be seen here.
Before 5.2 I use:
class Model
include ActiveModel::Attributes
attribute_reader :show_all
def show_all=(value)
#show_all = ActiveModel::Type::Boolean.new.cast(value)
end
end
Model.new(show_all: '0').show_all # => false
Another approach is to pass only the key without a value. Although using ActiveRecord::Type::Boolean.new.type_cast_from_user(value) is pretty neat, there might be a situation when assigning a value to the param key is redundant.
Consider the following:
On my products index view by default I want to show only scoped collection of products (e.g. those that are in the stock). That is if I want to return all the products, I may send myapp.com/products?show_all=true and typecast the show_all parameter for a boolean value.
However the opposite option - myapp.com/products?show_all=false just makes no sense since it will return the same product collection as myapp.com/products would have returned.
An alternative:
if I want to return the whole unscoped collection, then I send myapp.com/products?all and in my controller define
private
def show_all?
params.key?(:all)
end
If the key is present in params, then regardless of its value, I will know that I need to return all products, no need to typecast value.
You can add the following to your model:
def show_all= value
#show_all = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
You could convert all your boolean params to real booleans like this:
%w(show_all, show_featured).each do |bool_param|
params[bool_param.to_sym] = params[bool_param.to_sym] == "true"
end
In this solution, nil parameters would become false.
While not explicitly what the question is about I feel this is appropriately related; If you're trying to pass true boolean variables in a rails test then you're going to want the following syntax.
post :update, params: { id: user.id }, body: { attribute: true }.to_json, as: :json
I arrived at this thread looking for exactly this syntax, so I hope it helps someone looking for this as well. Credit to Lukom