How can i solve this: params is missing? - ruby-on-rails

I am using this function to find users, which i am using .require only worked when i sent both or at least one parameter but if i send empty i got errors, It should not be mandatory to send parameters
def find_params
params.require(:person).permit(:name, :age)
end
if i send the name or age i will work, but if i send nothing i am getting this error
params is missing
if i send empty like this:
{} or null it should work correctly returning all the users
or should not i use this to search users?
params.require(:person).permit(:name, :age)
might i have to user like this?
params[:name] and params[:age]
i am working with reactjs
i am sending the payload liket this:
{name:"ed", age:"12", skin:"black", weight: "180lbs", height:"183"}

Rails' StrongParameters were built for a very specific use:
It provides an interface for protecting attributes from end-user assignment. This makes Action Controller parameters forbidden to be used in Active Model mass assignment until they have been explicitly enumerated.
That means when you use the params just to read from the database, then there is no need (and hardly any advantage) to use StrongParameters.
Instead, I would just use the params directly in the controller like this:
def index
#users = User
.filter_by(:name, params.dig(:person, :name))
.filter_by(:age, params.dig(:person, :age))
# ...
end
And to make this work you will need to define an filter_by scope in your app/models/user.rb:
scope :filter_by, -> (attr, value) { where(attr => value) if value.present? }

The whole point of using ActionController::Parameters#require is to cause your create/update method to bail early if the parameter you expect to be a hash isn't sent at all since there is no point in proessing the request further and this prevents a potential uncaught nil error.
If you want to allow a key to be null use #fetch instead:
params.fetch(:person, {})
.permit(:name, :age)
#fetch allows you to pass a second key which is the default value and it returns a new ActionController::Parameters instance.
But it looks like you're actually sending flat parameters which are not nested in which case you don't need fetch either:
params.permit(:name, :age)
Note that Rails by default has parameters wrapping turned on for JSON requests and both will very likely work.

You can use Find User
Routes.rb
get "search_user", to: "users#search_user"
controllers/users_controller.rb
def search_user
#users = User.search(params[:name], params[:age], params[:skin], params[:weight], params[:height]) // search name or age
// you can use byebug to check #users
end
models/user.rb
def self.search(name, age, skin, weight, height)
if name.blank? & age.blank? & skin.blank? & weight.blank? & hight.blank?
all
else
where('name LIKE ? OR age LIKE ? OR skin LIKE ? OR weight LIKE ? OR height LIKE ?', "%#{name}%", "%#{age}%", "%#{skin}%", "%#{weight}%", "%#{height}%")
end
end
=> This is my way which i used. Hope to help you.

Related

Is it possible to pass a nested property of a hash to function in ruby

I have this function in rails controller:
def validate_params(*props)
props.each do |prop|
unless params[prop].start_with?('abc')
# return error
end
end
end
im thinking if I have params[:name] and params[:bio] and I want to validate name & bio with this function (not every attribute I might want to validate), I will call it with validate_params(:name, :bio). But, for nested param it won't work like params[:user][:name]. Is there anything I can do to pass this nested property to my function or is there a completely different approach? Thanks
Rails Validations generally belong in the model. You should post some additional info about what you're trying to do. For example, if you wanted to run the validation in the controller because these validations should only run in a certain context (i.e., only when this resource is interacted with from this specific endpoint), use on: to define custom contexts.
If you don't want to do things the rails way (which you should, imo), then don't call params in the method body. i.e.
def validate_params(*args)
args.each do |arg|
unless arg.start_with?('abc')
# return error
end
end
end
and call with validate_params(params[:user], params[:user][:name]
but yeah... just do it the rails way, you'll thank yourself later.

Monkeypatch ActiveRecord::FinderMethods

I'm trying to monkey patch ActiveRecord::FinderMethods in order to use hashed ids for my models. So for example User.find(1) becomes User.find("FEW"). Sadly my overwritten method doesn't get called. Any ideas how to overwrite the find_one method?
module ActiveRecord
module FinderMethods
alias_method :orig_find_one, :find_one
def find_one(id)
if id.is_a?(String)
orig_find_one decrypt_id(id)
else
orig_find_one(id)
end
end
end
end
Here's an article that discusses how to actually do what you want by overriding the User.primary_key method like:
class User
self.primary_key = 'hashed_id'
end
Which would allow you to call User.find and pass it the "hashed_id":
http://ruby-journal.com/how-to-override-default-primary-key-id-in-rails/
So, it's possible.
That said, I would recommend against doing that, and instead using something like User.find_by_hashed_id. The only difference is that this method will return nil when a result is not found instead of throwing an ActiveRecord::RecordNotFound exception. You could throw this manually in your controller:
def show
#user = User.find_by_hashed_id(hashed_id)
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
Finally, one other note to make this easier on you -- Rails also has a method you can override in your model, to_param, to tell it what property to use when generating routes. By default, of course, it users the id, but you would probably want to use the hashed_id.
class User
def to_param
self.hashed_id
end
end
Now, in your controller, params[:id] will contain the hashed_id instead of the id.
def show
#user = User.find_by_hashed_id(params[:id])
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
I agree that you should be careful when doing this, but it is possible.
If you have a method decode_id that converts a hashed ID back to the original id, then the following will work:
In User.rb
# Extend AR find method to allow finding records by an encoded string id:
def self.find(*ids)
return super if ids.length > 1
# Note the short-circuiting || to fall-back to default behavior
find_by(id: decode_id(ids[0])) || super
end
Just make sure that decode_id returns nil if it's passed an invalid hash. This way you can find by Hashed ID and standard ID, so if you had a user with id 12345, then the following:
User.find(12345)
User.find("12345")
User.find(encode_id(12345))
Should all return the same user.

How does params[:id] not throw an exception even if we do not explicitly whitelist :id?

In rails 4.x, strong_parameters require parameters to be explicitly permitted. Yet, in the following example, I do NOT get a ForbiddenAttributesError - why does :id not throw when in the show action even though it is not explicitly permitted?
def FooController
...
def show
#foo = Foo.find(params[:id]) # why no exception here?
end
private
def foo_params
params.require(:foo).permit(:name, :address) # note: No :id here
end
end
See: http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters
"With strong parameters, Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted."
Doing a find is completely valid, and is, in fact, shown in the example in the documentation linked to, above.
Strong parameters are used only for assignment of attributes. You can freely search and perform other operations with any param, just not mass assignment.
You can see more in-depth explanation and examples in Rails Guides
For Rails, params[:id] outside from default params.
Query string:
www.example.com/foo/123?bar=1&baz=2
Request path:
www.example.com/foo/123 where 123 is params[:id]
Paramerts:
bar=1&baz=2 this can be permitted
If you pass 123 to parameters then you need permitted :id.
There is no need of explicitly permitting the :id unless you want to.Rails will do it implicitly.If want to check whether the :id is whitelisted or not,you can do puts params[:foo] after it is created or you can just see the log.you will see something like this
{id=>some_id, "name"=>"some_name", "adddress"=>"some_address"}
So,defining a Foo object like this
#foo = Foo.find(params[:id])
will not throw an exception.
Hope it helped!

Ruby 2 Keyword Arguments and ActionController::Parameters

I have a rails 4 application that is running on ruby 2.1. I have a User model that looks something like
class User < ActiveModel::Base
def self.search(query: false, active: true, **extra)
# ...
end
end
As you can see in the search method I am attempting to use the new keyword arguments feature of ruby 2.
The problem is that when I call this code from in my controller all values get dumped into query.
params
{"action"=>"search", "controller"=>"users", query: "foobar" }
Please note that this is a ActionController::Parameters object and not a hash as it looks
UsersController
def search
#users = User.search(params)
end
I feel that this is because params is a ActionController::Parameters object and not a hash. However even calling to_h on params when passing it in dumps everything into query instead of the expected behavior. I think this is because the keys are now strings instead of symbols.
I know that I could build a new hash w/ symbols as the keys but this seems to be more trouble than it's worth. Ideas? Suggestions?
Keywords arguments must be passed as hash with symbols, not strings:
class Something
def initialize(one: nil)
end
end
irb(main):019:0> Something.new("one" => 1)
ArgumentError: wrong number of arguments (1 for 0)
ActionController::Parameters inherits from ActiveSupport::HashWithIndifferentAccess which defaults to string keys:
a = HashWithIndifferentAccess.new(one: 1)
=> {"one"=>1}
To make it symbols you can call symbolize_keys method. In your case: User.search(params.symbolize_keys)
I agree with Morgoth, however, with rails ~5 you will get a Deprecation Warning because ActionController::Parameters no longer inherits from hash. So instead you can do:
params.to_unsafe_hash.symbolize_keys
or if you have nested params as is often the case when building api endpoints:
params.to_unsafe_hash.deep_symbolize_keys
You might add a method to ApplicationController that looks something like this:
def unsafe_keyworded_params
#_unsafe_keyworded_params ||= params.to_unsafe_hash.deep_symbolized_keys
end
You most likely do need them to be symbols. Try this:
def search
#users = User.search(params.inject({}){|para,(k,v)| para[k.to_sym] = v; para}
end
I know it's not the ideal solution, but it is a one liner.
In this particular instance I think you're better off passing the params object and treating it as such rather than trying to be clever with the new functionality in Ruby 2.
For one thing, reading this is a lot clearer about where the variables are coming from and why they might be missing/incorrect/whatever:
def search(params)
raise ArgumentError, 'Required arguments are missing' unless params[:query].present?
# ... do stuff ...
end
What you're trying to do (in my opinion) only clouds the issue and confuses things when trying to debug problems:
def self.search(query: false, active: true, **extra)
# ...
end
# Method explicitly asks for particular arguments, but then you call it like this:
User.search(params)
Personally, I think that code is a bit smelly.
However ... personal opinion aside, how I would fix it would be to monkey-patch the ActionController::Parameters class and add a #to_h method which structured the data as you need it to pass to a method like this.
Using to_unsafe_hash is unsafe because it includes params that are not permitted. (See ActionController::Parameters#permit) A better approach is to use to_hash:
params.to_hash.symbolize_keys
or if you have nested params:
params.to_hash.deep_symbolize_keys
Reference: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-to_hash

Bind paramerts in Object if those present there

I am receiving an API call at my server with parameters
first_name , :last_name , :age
etc
I want to bind those params to my object against which user is having attribute with same name , like i want to have these in user[first_name] , user[:last_name]
so that I can just put the complete user object into database in following way ,
User.new(params[:user]) or User.new(some_hash)
I dont want to use the following ,
User.new(:first_name=>params[:first_name],:last_name=>params[:last_name])
thanks in advance for you help :)
Something like this may work:
user = User.new
params.each do |key,value|
user[key] = value if user.attribute_names.include?(key.to_s)
end
Note, however, that you should protect sensitive attributes of your User model with attr_protected or attr_accessible in this case.
Writing that functionality into User.initialize can take care of this:
def initialize(args={})
args.each_with_key do |key,val|
instance_variable_set("##{key}", val)
end
end
This of course has no validation and does not protect your object from bad data. For example, if you want to make sure only valid accessible attributes are being set, add if respond_to? key to end end of line 3.

Resources