Some valid ActiveRecord objects return false for present?:
object.nil? # => false
object.valid? # => true
object.present? # => false
object.blank? # => true
I prefer object.present? over not object.nil?. What determines the return value of present?/blank??
EDIT: Found the answer: I had redefined the empty? method on this class, not the blank? or present? method; along the lines of #Simone's answer, blank? is using empty? behind the scenes.
present? is the opposite of blank?. blank? implementation depends on the type of object. Generally speaking, it returns true when the value is empty or like-empty.
You can get an idea looking at the tests for the method:
BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', "\u00a0", [], {} ]
NOT = [ EmptyFalse.new, Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ]
def test_blank
BLANK.each { |v| assert_equal true, v.blank?, "#{v.inspect} should be blank" }
NOT.each { |v| assert_equal false, v.blank?, "#{v.inspect} should not be blank" }
end
def test_present
BLANK.each { |v| assert_equal false, v.present?, "#{v.inspect} should not be present" }
NOT.each { |v| assert_equal true, v.present?, "#{v.inspect} should be present" }
end
An object can define its own interpretation of blank?. For example
class Foo
def initialize(value)
#value = value
end
def blank?
#value != "foo"
end
end
Foo.new("bar").blank?
# => true
Foo.new("foo").blank?
# => false
If not specified, it will fallback to the closest implementation (e.g. Object).
From the documentation for Object#present?
An object is present if it's not blank.
In your case, object is blank, so present? will return false. Validity of the object does not really matter.
Related
I have a string of field names like this "name,address.postal_code" that acts as a whitelist for a hash that looks like this:
{
name: "Test",
email: "test#test.com",
address: {
postal_code: "12345",
street: "Teststreet"
}
}
So now what I want to to is convert the whitelist string into a format that is accepted by ActionController::Parameters.permit: [:name, address: [:postal_code]]
What would be the best way to do this? I've tried group_by and some other things but it always turned out way more complicated that I think it needs to be.
This uses Array#group_by to parse the nested keys recursively:
module WhitelistParser
# Parses a comma delimed string into an array that can be passed as arguments
# to the rails params whitelist
def self.parse(string)
ary = string.split(',').map do |key|
if key.include?('.')
key.split('.').map(&:intern)
else
key.intern
end
end
make_argument_list(ary)
end
private
def self.make_argument_list(ary)
nested, flat = ary.partition { |a| a.is_a?(Array) }
if flat.any?
flat.tap do |a|
a.push(make_hash(nested)) if nested.any?
end
elsif nested.any?
make_hash(nested)
end
end
def self.make_hash(nested)
nested.group_by(&:first).transform_values do |value|
make_argument_list(value.map { |f, *r| r.length > 1 ? r : r.first })
end
end
end
Usage:
irb(main):004:0> params = ActionController::Parameters.new(foo: { a: 1, b: 2, bar: { baz: 3} })
irb(main):005:0> whitelist = WhitelistParser.parse('a,b,bar.baz')
irb(main):006:0> params.require(:foo).permit(*whitelist)
=> <ActionController::Parameters {"a"=>1, "b"=>2, "bar"=><ActionController::Parameters {"baz"=>3} permitted: true>} permitted: true>
Spec:
require 'spec_helper'
RSpec.describe WhitelistParser do
describe ".parse" do
let(:string) { "name,address.postal_code,foo.bar,foo.bar.baz" }
it "handles flat arguments" do
expect(WhitelistParser.parse(string)).to include :name
end
it "handles hash arguments" do
expect(WhitelistParser.parse(string).last).to include(
{ address: [:postal_code] }
)
end
it "handles nested hash arguments" do
expect(WhitelistParser.parse(string).last[:foo]).to include(
{ bar: [:baz] }
)
end
end
end
It's not ideal, but it should work:
def to_strong_parameters_compatible(fields)
fields # fields="name,address.postal_code,address.street"
.split(',') # ["name", "address.postal_code", "address.street"]
.group_by { |val| val.split('.').first } # { "name" => ["name"], "address" => ["address.postal_code", "address.street"]}
.inject([]) do |params, hash|
k = hash.first
v = hash.last
puts "k: #{k}"
puts "v: #{v}"
# 1st: k="name", v=["name"]
# 2nd: k="address", v=["address.postal_code", "address.street"]
if v.length == 1
params << k
# 1st: params=["name"]
else
params << { k => v.map { |chain| chain.split('.').last } }
# 2nd: params=["name", { "address" => ["postal_code", "street"]}]
end
params
end
end
fields = "name,address.postal_code,address.street"
# How you should use it:
params.permit(*to_strong_parameters_compatible(fields))
Note: it won't work for fields like a.b.c... you would have to make this algo recursive (it should not be hard)
EDIT: and here is the recursive version
def to_strong_parameters_compatible(fields)
fields
.split(',')
.group_by { |val| val.split('.').first }
.inject([]) do |params, hash|
k = hash.first
v = hash.last
if v.length == 1
params << k
else
params << {
k => to_strong_parameters_compatible(
v
.map { |chain| chain.split('.').drop(1).join('.') }
.join(',')
)
}
end
params
end
end
fields = "name,address.postal_code,address.street,address.country.code,address.country.name"
to_strong_parameters_compatible(fields)
# ["name", {"address"=>["postal_code", "street", {"country"=>["code", "name"]}]}]
# How you should use it:
params.permit(*to_strong_parameters_compatible(fields))
Using the hyperstack.org framework, how can I reduce the rendering cycles when mutating models that are being rendered?
When passing a Model which is being rendered to a Component which mutates that Model, all Components rendering that Model get re-rendered on any mutation. This is fine unless the mutation is per key press as this means that all Components get re-rendered per key press.
For example, if we have this table:
class UserIndex < HyperComponent
render(DIV) do
puts "UserIndex render"
BridgeAppBar()
UserDialog(user: User.new)
Table do
TableHead do
TableRow do
TableCell { 'Name' }
TableCell { 'Gender' }
TableCell { 'Edit' }
end
end
TableBody do
user_rows
end
end
end
def user_rows
User.each do |user|
TableRow do
TableCell { "#{user.first_name} #{user.last_name}" }
TableCell { user.is_female ? 'Female' : 'Male' }
TableCell { UserDialog(user: user) }
end
end
end
end
And this Compnent (which is used for edit and new):
class UserDialog < HyperComponent
param :user
before_mount do
#open = false
end
render do
puts "UserDialog render"
if #open
render_dialog
else
edit_or_new_button.on(:click) { mutate #open = true }
end
end
def render_dialog
Dialog(open: #open, fullWidth: false) do
DialogTitle do
'User'
end
DialogContent do
content
error_messages if #User.errors.any?
end
DialogActions do
actions
end
end
end
def edit_or_new_button
if #User.new?
Fab(size: :small, color: :primary) { Icon { 'add' } }
else
Fab(size: :small, color: :secondary) { Icon { 'settings' } }
end
end
def content
FormGroup(row: true) do
TextField(label: 'First Name', defaultValue: #User.first_name.to_s).on(:change) do |e|
#User.first_name = e.target.value
end
TextField(label: 'Last Name', defaultValue: #User.last_name.to_s).on(:change) do |e|
#User.last_name = e.target.value
end
end
BR()
FormLabel(component: 'legend') { 'Gender' }
RadioGroup(row: true) do
FormControlLabel(label: 'Male',
control: Radio(value: false, checked: !#User.is_female).as_node.to_n)
FormControlLabel(label: 'Female',
control: Radio(value: true, checked: #User.is_female).as_node.to_n)
end.on(:change) do |e|
#User.is_female = e.target.value
end
end
def actions
Button { 'Cancel' }.on(:click) { cancel }
if #User.changed? && validate_content
Button(color: :primary, variant: :contained, disabled: (#User.saving? ? true : false)) do
'Save'
end.on(:click) { save }
end
end
def save
#User.save(validate: true).then do |result|
mutate #open = false if result[:success]
end
end
def cancel
#User.revert
mutate #open = false
end
def error_messages
#User.errors.full_messages.each do |message|
Typography(variant: :h6, color: :secondary) { message }
end
end
def validate_content
return false if #User.first_name.to_s.empty?
return false if #User.last_name.to_s.empty?
return false if #User.is_female.nil?
true
end
end
The underlying table (from the first code example) is re-rendered on every keypress, caused by:
TextField(label: 'First Name', defaultValue: #User.first_name.to_s)
.on(:change) do |e|
#User.first_name = e.target.value
end
This is causing typing to appear sluggish due to the amount of re-rendering.
Should I be keeping a local state variable for each field then only mutating the model fields on save?
Looks like you are using Material UI which will dynamically size tables to be best fit the content. So I suspect what is happening is that you are displaying the value of first_name and last_name in the MUI table, while you editing the values in the Dialog box.
So MUI is constantly recalculating the size of the MUI table columns as each character is typed.
Not only is this going to slow things down, but its also going to be disconcerting to the human user. It will give the impression that the changes they are making all already taking effect even before you have saved them.
So yes I think the best approach is to not directly update the state of the record while the user is typing but rather update a local state variable. Then only when the user saves, do you update the actual record.
I do notice that you have defaultValue which indicates an "uncontrolled" input. But you are reacting to every change in the input, which is the "controlled" behavior. I think you can change defaultValue to value.
class UserDialog < HyperComponent
param :user
before_mount do
#open = false
#first_name = #User.first_name
#last_name = #User.last_name
#is_female = #User.is_female
end
render do
puts "UserDialog render"
if #open
render_dialog
else
edit_or_new_button.on(:click) { mutate #open = true }
end
end
def render_dialog
Dialog(open: #open, fullWidth: false) do
DialogTitle do
'User'
end
DialogContent do
content
error_messages if #User.errors.any?
end
DialogActions do
actions
end
end
end
def edit_or_new_button
if #User.new?
Fab(size: :small, color: :primary) { Icon { 'add' } }
else
Fab(size: :small, color: :secondary) { Icon { 'settings' } }
end
end
def content
FormGroup(row: true) do
TextField(label: 'First Name', value: #first_name).on(:change) do |e|
mutate #first_name = e.target.value
end
TextField(label: 'Last Name', value: #last_name).on(:change) do |e|
mutate #last_name = e.target.value
end
end
BR()
FormLabel(component: 'legend') { 'Gender' }
RadioGroup(row: true) do
FormControlLabel(label: 'Male',
control: Radio(value: false, checked: !#is_female).as_node.to_n)
FormControlLabel(label: 'Female',
control: Radio(value: true, checked: #is_female).as_node.to_n)
end.on(:change) do |e|
mutate #is_female = e.target.value
end
end
def actions
Button { 'Cancel' }.on(:click) { cancel }
return unless ready_to_save?
Button(color: :primary, variant: :contained, disabled: (#User.saving? ? true : false)) do
'Save'
end.on(:click, &:save)
end
def save
#User.update(first_name: #first_name, last_name: #last_name, is_female: #is_female).then do |result|
mutate #open = false if result[:success]
end
end
def cancel
mutate #open = false
end
def error_messages
#User.errors.full_messages.each do |message|
Typography(variant: :h6, color: :secondary) { message }
end
end
def ready_to_save?
return false if #first_name.empty?
return false if #last_name.empty?
return false if #is_female.nil?
return true if #first_name != #User.first_name
return true if #last_name != #User.last_name
return true if #is_female != #User.is_female
end
end
As it turned out the things that were causing the performance problem was that I was not passing a unique key to the items in the list. React is very particular about this yet this is not something you get warnings about.
All I had to change was:
User.each do |user|
TableRow do
...
TableCell { UserDialog(user: user) }
end
end
To:
User.each do |user|
TableRow do
...
# this passes a unique key to each Component
TableCell { UserDialog(user: user, key: user) }
end
end
With the above change everything works perfectly in both examples (the first being where the underlying table is updated as the user types and the second, provided by #catmando, where the changes are only applied on save.
I have an object MyObject:
class MyObject
def initialize(options = {})
#stat_to_load = options[:stat_to_load] || 'test'
end
def results
[]
end
end
I want to stub the results method only if stat_to_load = "times". How can I do that? I tried:
MyObject.any_instance.stubs(:initialize).with({
:stat_to_load => "times"
}).stubs(:results).returns(["klala"])
but it does not work. Any idea?
So, I think there is probably a simpler way to test what you're trying to test, but without more context I don't know what to recommend. However, here is some proof-of-concept code to show that what you want to do can be done:
describe "test" do
class TestClass
attr_accessor :opts
def initialize(opts={})
#opts = opts
end
def bar
[]
end
end
let!(:stubbed) do
TestClass.new(args).tap{|obj| obj.stub(:bar).and_return("bar")}
end
let!(:unstubbed) { TestClass.new(args) }
before :each do
TestClass.stub(:new) do |args|
case args
when { :foo => "foo" }
stubbed
else
unstubbed
end
end
end
subject { TestClass.new(args) }
context "special arguments" do
let(:args) { { :foo => "foo" } }
its(:bar) { should eq "bar" }
its(:opts) { should eq({ :foo => "foo" }) }
end
context "no special arguments" do
let(:args) { { :baz => "baz" } }
its(:bar) { should eq [] }
its(:opts) { should eq({ :baz => "baz" }) }
end
end
test
special arguments
bar
should == bar
opts
should == {:foo=>"foo"}
no special arguments
bar
should == []
opts
should == {:baz=>"baz"}
Finished in 0.01117 seconds
4 examples, 0 failures
However I'm making a lot of use of special subject/let context blocks here. See http://benscheirman.com/2011/05/dry-up-your-rspec-files-with-subject-let-blocks/ for more on that subject.
Try out below, this should work as expected:
Here, Basically we are actually stubbing new instance getting created and also stubbing results method of the instance which is getting returned.
options = {:stat_to_load => "times"}
MyObject.stubs(:new).with(options)
.returns(MyObject.new(options).stubs(:results).return(["klala"]))
You could use plain old Ruby inside your test to achieve this.
MyObject.class_eval do
alias_method :original_results, :results
define_method(:results?) do
if stats_to_load == "times"
["klala"]
else
original_results
end
end
end
controller code:
def create
if current_user.id != params[:friend_id]
#return = { :curr_user_id => current_user.id, :params_id => params[:friend_id], :debug => 1 }
else
#return = { :curr_user_id => current_user.id, :params_id => params[:friend_id], :debug => 2 }
end
render :json => ActiveSupport::JSON.encode( #return )
end
so current_user.id is 1 and params[:friend_id] is being passed via an ajax call and its value is also 1
the problem is that it always returns true when it should return false in this case... is there anything that has to do with the number 1 being parsed as string instead of integer?
params are always strings, use:
if current_user.id != Integer(params[:friend_id])
I don't recommend to_i, look why:
"abc".to_i # => 0 which is unexpected
Integer("abc") # => raises error, which is fine
I'm using accepts_nested_attributes_for with :allow_destroy => true.
When deleting an object, I can see that the attribute _destroy is marked as true, but when I'm checking my object with object.destroyed?, I'm getting nil instead of true.
Any ideas why?
From the doc:
Now, when you add the _destroy key to the attributes hash, with a value that evaluates to true, you will destroy the associated model:
member.avatar_attributes = { :id => '2', :_destroy => '1' }
member.avatar.marked_for_destruction? # => true
destroyed? is here to check if the object is actually destroyed:
foo = Foo.first
foo.destroyed #=> false
foo.destroy
foo.destroyed? #=> true