I'm learning Ruby on Rails and got curious how the params method works. I understand what it does, but how?
Is there a built-in method that takes a hash string like so
"cat[name]"
and translates it to
{ :cat => { :name => <assigned_value> } }
?
I have attempted to write the params method myself but am not sure how to write this functionality in ruby.
The GET parameters are set from ActionDispatch::Request#GET, which extends Rack::Request#GET, which uses Rack::QueryParser#parse_nested_query.
The POST parameters are set from ActionDispatch::Request#POST, which extends Rack::Request#POST, which uses Rack::Multipart#parse_multipart. That splays through several more files in lib/rack/multipart.
Here is a reproduction of the functionality of the method (note: this is NOT how the method works). Helper methods of interest: #array_to_hash and #handle_nested_hash_array
require 'uri'
class Params
def initialize(req, route_params = {})
#params = {}
route_params.keys.each do |key|
handle_nested_hash_array([{key => route_params[key]}])
end
parse_www_encoded_form(req.query_string) if req.query_string
parse_www_encoded_form(req.body) if req.body
end
def [](key)
#params[key.to_sym] || #params[key.to_s]
end
def to_s
#params.to_s
end
class AttributeNotFoundError < ArgumentError; end;
private
def parse_www_encoded_form(www_encoded_form)
params_array = URI::decode_www_form(www_encoded_form).map do |k, v|
[parse_key(k), v]
end
params_array.map! do |sub_array|
array_to_hash(sub_array.flatten)
end
handle_nested_hash_array(params_array)
end
def handle_nested_hash_array(params_array)
params_array.each do |working_hash|
params = #params
while true
if params.keys.include?(working_hash.keys[0])
params = params[working_hash.keys[0]]
working_hash = working_hash[working_hash.keys[0]]
else
break
end
break if !working_hash.values[0].is_a?(Hash)
break if !params.values[0].is_a?(Hash)
end
params.merge!(working_hash)
end
end
def array_to_hash(params_array)
return params_array.join if params_array.length == 1
hash = {}
hash[params_array[0]] = array_to_hash(params_array.drop(1))
hash
end
def parse_key(key)
key.split(/\]\[|\[|\]/)
end
end
Related
I have QueryType
Types::QueryType = GraphQL::ObjectType.define do
name 'Query'
field :allProjects, function: Resolvers::Projects
end
And Resolver like this
require 'search_object/plugin/graphql'
module Resolvers
class Projects
include SearchObject.module(:graphql)
type !types[Types::ProjectType]
scope { Project.all }
ProjectFilter = GraphQL::InputObjectType.define do
name 'ProjectFilter'
argument :OR, -> { types[ProjectFilter] }
argument :description_contains, types.String
argument :title_contains, types.String
end
option :filter, type: ProjectFilter, with: :apply_filter
option :first, type: types.Int, with: :apply_first
option :skip, type: types.Int, with: :apply_skip
def apply_first(scope, value)
scope.limit(value)
end
def apply_skip(scope, value)
scope.offset(value)
end
def apply_filter(scope, value)
branches = normalize_filters(value).reduce { |a, b| a.or(b) }
scope.merge branches
end
def normalize_filters(value, branches = [])
scope = Project.all
scope = scope.where('description ILIKE ?', "%#{value['description_contains']}%") if value['description_contains']
scope = scope.where('title ILIKE ?', "%#{value['title_contains']}%") if value['title_contains']
branches << scope
value['OR'].reduce(branches) { |s, v| normalize_filters(v, s) } if value['OR'].present?
branches
end
end
end
I want to access current_user in the resolver so i can access current_user.projects not Project.all. I am very new to graphql and learning.
Everything works but i just need to understand the whole flow on how i can get old of the ctx in the resolver.
First you need to set the current_user in the context. This happens in your GraphqlController.
class GraphqlController < ApplicationController
before_action :authenticate_user!
def execute
variables = ensure_hash(params[:variables])
query = params[:query]
operation_name = params[:operationName]
context = {
current_user: current_user,
}
result = HabitTrackerSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
render json: result
rescue => e
raise e unless Rails.env.development?
handle_error_in_development e
end
# ...
end
Once it's done, you can access the current_user from a query (or a mutation) simply by writing:
context[:current_user]
To make things even simpler, you can add a current_user method toTypes::BaseObject (app/graphql/types/base_object.rb) and you'll be able to call current_user from the #resolve methods.
module Types
class BaseObject < GraphQL::Schema::Object
field_class Types::BaseField
def current_user
context[:current_user]
end
end
end
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I followed tutorial how to integrate 3rd party api with a ruby on rails but I get an error
undefined method `map' for
{"number"=>12} permitted: false>:ActionController::Parameters
which points to request.rb
query_string = query.map{|k,v| "#{k}=#{v}"}.join("&")
Full code
recipes_controller.rb
class RecipesController < ApplicationController
def index
#tag = query.fetch(:tags, 'all')
#refresh_params = refresh_params
#recipes, #errors = Spoonacular::Recipe.random(query, clear_cache)
end
def show
#recipe = Spoonacular::Recipe.find(params[:id])
end
private
def query
params.permit(:query).fetch(:query, {})
end
def clear_cache
params[:clear_cache].present?
end
def refresh_params
refresh = { clear_cache: true }
refresh.merge!({ query: query }) if query.present?
refresh
end
end
app/services/spoonacular/recipes.rb
module Spoonacular
class Recipe < Base
attr_accessor :aggregate_likes,
:dairy_free,
:gluten_free,
:id,
:image,
:ingredients,
:instructions,
:ready_in_minutes,
:title,
:vegan,
:vegetarian
MAX_LIMIT = 12
CACHE_DEFAULTS = { expires_in: 7.days, force: false }
def self.random(query = {}, clear_cache)
cache = CACHE_DEFAULTS.merge({ force: clear_cache })
response = Spoonacular::Request.where('recipes/random', cache, query.merge({ number: MAX_LIMIT }))
recipes = response.fetch('recipes', []).map { |recipe| Recipe.new(recipe) }
[ recipes, response[:errors] ]
end
def self.find(id)
response = Spoonacular::Request.get("recipes/#{id}/information", CACHE_DEFAULTS)
Recipe.new(response)
end
def initialize(args = {})
super(args)
self.ingredients = parse_ingredients(args)
self.instructions = parse_instructions(args)
end
def parse_ingredients(args = {})
args.fetch("extendedIngredients", []).map { |ingredient| Ingredient.new(ingredient) }
end
def parse_instructions(args = {})
instructions = args.fetch("analyzedInstructions", [])
if instructions.present?
steps = instructions.first.fetch("steps", [])
steps.map { |instruction| Instruction.new(instruction) }
else
[]
end
end
end
end
app/services/spoonacular/base.rb
module Spoonacular
class Base
attr_accessor :errors
def initialize(args = {})
args.each do |name, value|
attr_name = name.to_s.underscore
send("#{attr_name}=", value) if respond_to?("#{attr_name}=")
end
end
end
end
app/services/spoonacular/request.rb
module Spoonacular
class Request
class << self
def where(resource_path, cache, query = {}, options = {})
response, status = get_json(resource_path, cache, query)
status == 200 ? response : errors(response)
end
def get(id, cache)
response, status = get_json(id, cache)
status == 200 ? response : errors(response)
end
def errors(response)
error = { errors: { status: response["status"], message: response["message"] } }
response.merge(error)
end
def get_json(root_path, cache, query = {})
query_string = query.map{|k,v| "#{k}=#{v}"}.join("&")
path = query.empty?? root_path : "#{root_path}?#{query_string}"
response = Rails.cache.fetch(path, expires_in: cache[:expires_in], force: cache[:force]) do
api.get(path)
end
[JSON.parse(response.body), response.status]
end
def api
Connection.api
end
end
end
end
app/services/spoonacular/connection.rb
require 'faraday'
require 'json'
module Spoonacular
class Connection
BASE = 'https://spoonacular-recipe-food-nutrition-v1.p.mashape.com'
def self.api
Faraday.new(url: BASE) do |faraday|
faraday.response :logger
faraday.adapter Faraday.default_adapter
faraday.headers['Content-Type'] = 'application/json'
faraday.headers['X-Mashape-Key'] ='key'
end
end
end
end
Thank you for any help.
You have 2 separate errors here.
uninitialized constant Spoonacular::Recipe::Request
This one you can fix by explicitly setting top-level scope for Request class:
::Request.where(...)
It applies if you keep Request file in app/spoonacular/request.rb. But I suggest to move it to app/services/spoonacular/ where all your other spoonacular related classes are. So in this case you need to encircle class Request in module Spoonacular. After that you can call it like that:
Spoonacular::Request.where(...)
Same goes for class Connection.
SO answer about scope resolution operator
undefined method `map' for {"number"=>12} permitted:
false>:ActionController::Parameters
This one comes from private query method in recipes_controller.rb. params is ActionController::Parameters object and in order to retrieve values from it you need to permit them first:
def query
params.permit(:query).to_h
end
Now it should return Hash object.
Here is detailed answer on SO about that
RubyOnRails Guide about strong params
Weird problem. If the class at the bottom was a module, split the Json without problems, if it was only methods, also works, but the problem is.. when it is a class, it does not split the Json anymore, and returns an empty array.. however, if being a class, I do a puts the object, it actually puts it..
Any thoughts about why? How can I fix it?
I have this controller:
def index
begin
call_employee_work_locations_api
rescue => ex
render :json => {"service unavailable": "0001" }, :status => :service_unavailable
end
end
I have this service:
def call_employee_work_locations_api
auth = {:username=>ENV["USERNAME"], :password=>ENV["PASSWORD"]}
employee_locations = HTTParty.get(employee_work_Location_url , :basic_auth => auth)
#serialize_work_location(employee_locations)
serializer = EmployeeSerializer.new
serializer.serialize_work_location(employee_locations)
end
I have this builder:
json.array!(#top_locations) do |location|
json.extract! location, :name, :description, :latitude, :longitude
end
I have this class:
class EmployeeSerializer
def serialize_work_location(employee_locations)
employee_locations= JSON.parse(employee_locations)
locations=[]
employee_locations["work_locations"].each do |attributes|
location = Location.new(attributes["latitude"],attributes["longitude"],attributes["description"],attributes["name"])
locations.push(location)
end
employee_locations_selector(locations)
end
def top_office_location_selector(locations, city)
top_locations=[]
locations.each do |office|
if office.name == city[0] then top_locations.push(office) end
if office.name == city[1] then top_locations.push(office) end
end
#top_locations = top_locations
p #top_locations <--- it prints the object perfectly, but does not pass to the view, I get an empty array instead.
end
def employee_locations_selector(locations)
city = locations.each_with_object(Hash.new(0)) { |locations, counts| counts[locations.name] += 1 }.max_by{|k,v| v}
top_office_location_selector(locations, city)
end
end
The instance variable #top_locations is being set within the scope of the EmployeeSerializer class, not your controller. As such it's just a normal instance variable and so Rails knows nothing about it. You can assign the return value of #top_office_location_selector to an instance variable in the controller and it should work.
On a side note, the code would be cleaned up a lot by using #map over #each.
I'm aware I can do something like this:
#object.update_attributes(date: params[:date]) if params[:date].present?
#object.update_attributes(date: params[:name]) if params[:name].present?
#object.update_attributes(date: params[:thing]) if params[:thing].present?
#object.update_attributes(date: params[:item]) if params[:item].present?
But is there a way to do all of this at once?
Something like:
#object.update_attributes(object_params)
where it won't put nulls in my database if they aren't passed in through the url. So I can call
Object.update(date: Date.today, name: "ryan") and it will only update these items.
You can try this
#object.update_attributes(params)
Also, it's a good practice to use strong params, putting this in the end of your controller
private
def object_params
params.require(:object).permit(:date, :name, :item, :thing)
end
end
and use it as
#object.update_attributes(object_params)
Just select params which are not nil in your controller and use #object.update_attributes(object_params) as usual
def object_params
params.require(:object).permit(:date, :name, :thing, :item).select { |k, v| !v.nil? }
end
Try this:
keys = [:date, :name, :item, :thing]
object_params = params.slice(*keys).delete_if { |k,v| v.nil? }
#object.update_attributes(object_params)
You could also do this if the param keys match the model columns:
object_params = params.slice(*Model.column_names).delete_if { |k,v| v.nil? }
#object.update_attributes(object_params)
Controller:
user = User.find(params[:id])
respond_with({:posts => #posts.as_json})
Model:
def as_json(options = {})
{
name: self.name,
...
}
end
I want to pass parmeters like params[:id] to the as_json function to change things in the JSON display.
How can I do it?
Well, as_json does take an options hash, so I suppose you could call it using
respond_with({:posts => #posts.as_json(:params => params)})
You'd then be able to reference the params in the definition of as_json:
def as_json(options = {})
params = options[:params] || {}
{
name: self.name,
params_id: params[:id]
...
}
end