Ruby on Rails and JSON request processing - ruby-on-rails

I have ruby on rails app and my controller should process request which creates many objects. Objects data is passed from client via json using POST method.
Example of my request (log from controller):
Processing by PersonsController#save_all as JSON
Parameters: {"_json"=>[{"date"=>"9/15/2014", "name"=>"John"},
{"date"=>"9/15/2014", "name"=>"Mike"}], "person"=>{}}
So i need to save these two users but i have some issues:
How to verify strong parameters here? Only Name and Date attributes can be passed from client
How can I convert String to Date if i use Person.new(params)?
Can i somehow preprocess my json? For example i want to replace name="Mike" to name="Mike User" and only then pass it in my model
I want to enrich params of every person by adding some default parameters, for example, i want to add status="new_created" to person params

First of all I'd name the root param something like "users", then it gives a structure that is all connected to the controller name and the data being sent.
Regarding strong params. The config depends of your rails app version. <= 3.x doesn't have this included so you need to add the gem. If you're on >= 4.x then this is already part of rails.
Next in your controller you need to define a method that will do the filtering of the params you need. I should look something like:
class PeopleController < ApplicationController
def some_action
# Here you can call a service that receives people_params and takes
# care of the creation.
if PeopleService.new(people_params).perform
# some logic
else
# some logic
end
end
private
def base_people_params
params.permit(people: [:name, :date])
end
# Usually if you don't want to manipulate the params then call the method
# just #people_params
def people_params
base_people_params.merge(people: normalized_params)
end
# In case you decided to manipulate the params then create small methods
# that would that separately. This way you would be able to understand this
# logic when returning to this code in a couple of months.
def normalized_params
return [] unless params[:people]
params[:people].each_with_object([]) do |result, person|
result << {
name: normalize_name(person[:name]),
date: normalize_date(person[:date]),
}
end
end
def normalize_date(date)
Time.parse(date)
end
def normalize_name(name)
"#{name} - User"
end
end
If you see that the code starts to get to customized take into a service. It will help to help to keep you controller thin (and healthy).
When you create one reason at the time (and not a batch like here) the code is a bit simpler, you work with hashes instead of arrays... but it's all pretty much the same.
EDIT:
If you don't need to manipulate a specific param then just don't
def normalized_params
return [] unless params[:people]
params[:people].each_with_object([]) do |result, person|
result << {
name: person[:name],
date: normalize_date(person[:date]),
}
end
end

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.

Storing json api calls in Rails

I have built an app that consumes a json api. I removed active record from my app because the data in the api can theoretically change and I don't want to wipe the database each time.
Right now I have a method called self.all for each class that loops through the json creating ruby objects. I then call that method in various functions in order to work with the data finding sums and percentages. This all works fine, but seems a bit slow. I was wondering if there is somewhere I should be storing my .all call rather than instantiating new objects for each method that works with the data.
...response was assign above using HTTParty...
def self.all
puppies = []
if response.success?
response['puppies'].each do |puppy|
accounts << new(puppy['name'],
puppy['price'].to_money,
puppy['DOB'])
end
else
raise response.response
end
accounts
end
# the methods below only accept arguments to allow testing with Factories
# puppies is passed in as Puppy.all
def self.sum(puppies)
# returns money object
sum = Money.new(0, 'USD')
puppies.each do |puppy|
sum += puppy.price
end
sum
end
def self.prices(puppies)
prices = puppies.map { |puppy| puppy.price }
end
def self.names(puppies)
names = puppies.map { |puppy| puppy.name }
end
....many more methods that take an argument of Puppy.all in the controller....
Should I use cacheing? should I bring back active record? or is how I'm doing it fine? Should I store Puppy.all somewhere rather than calling the method each time?
What I guess is happening is that you are making a request with HTTParty every time you call any class method. What you can consider is creating a class variable for the response and a class variable called expires_at. Then you can do some basic caching.
##expires_at = Time.zone.now
##http_response
def make_http_call
renew_http_response if ##expires_at.past?
end
def renew_http_response
# make HTTParty request here
##http_response = # HTTParty response
##expires_at = 30.minutes.from_now
end
# And in your code, change response to ##response
# ie response.success? to ##response.success?
This is all in memory and you lose everything if you restart your server. If you want more robust caching, the better thing to do would probably to look into rails low-level caching

delayed_job: how to check for presence of a particular job based on a triggered method

I have a method like this that goes through an array to find different APIs and launch a delayed_job instance for every API found like this.
def refresh_users_list
apis_array.each do |api|
api.myclass.new.delay.get_and_create_or_update_users
end
end
I have an after_filter on users#index controller to trigger this method. This is creating many jobs to be triggered that will eventually cause too many connections problems on Heroku.
I'm wondering if there's a way I can check for the presence of a Job in the database by each of the API that the array iterates. This would be very helpful so I can only trigger a particular refresh if that api wasn't updated on a given time.
Any idea how to do this?
In config/application.rb, add the following
config.autoload_paths += Dir["#{config.root}/app/jobs/**/"]
Create a new directory at app/jobs/.
Create a file at app/jobs/api_job.rb that looks like
class ApiJob < Struct.new(:attr1, :attr2, :attr3)
attr_accessor :token
def initialize(*attrs)
self.token = self.class.token(attr1, attr2, attr3)
end
def display_name
self.class.token(attr1, attr2, attr3)
end
#
# Class methods
#
def self.token(attr1, attr2, attr3)
[name.parameterize, attr1.id, attr2.id, attr3.id].join("/")
end
def self.find_by_token(token)
Delayed::Job.where("handler like ?", "%token: #{token}%")
end
end
Note: You will replace attr1, attr2, and attr3 with whatever number of attributes you need (if any) to pass to the ApiJob to perform the queued task. More on how to call this in a moment
For each of your API's that you queue some get_and_create_or_update_users method for you'll create another Job. For example, if I have some Facebook api model, I might have a class at app/jobs/facebook_api_job.rb that looks like
class FacebookApiJob < ApiJob
def perform
FacebookApi.new.get_and_create_or_update_users(attr1, attr2, attr3)
end
end
Note: In your Question you did not pass any attributes to get_and_create_or_update_users. I am just showing you where you would do this if you need the job to have attributes passed to it.
Finally, wherever your refresh_users_list is defined, define something like this job_exists? method
def job_exists?(tokens)
tokens = [tokens] if !tokens.is_a?(Array) # allows a String or Array of tokens to be passed
tokens.each do |token|
return true unless ApiJob.find_by_token(token).empty?
end
false
end
Now, within your refresh_users_list and loop, you can build new tokens and call job_exists? to check if you have queued jobs for the API. For example
# Build a token
def refresh_users_list
apis_array.each do |api|
token = ApiJob.token(attr1, attr2, attr3)
next if job_exists?(token)
api.myclass.new.delay.get_and_create_or_update_users
end
end
Note: Again I want to point out, you won't be able to just drop in the code above and have it work. You must tailor it to your application and the job's you're running.
Why is this so complicated?
From my research, there's no way to "tag" or uniquely identify a queued job through what delayed_job provides. Sure, each job has a unique :id attribute. You could store the ID values for each created job in some hash somewhere
{
"FacebookApi": [1, 4, 12],
"TwitterApi": [3, 193, 44],
# ...
}
and then check corresponding hash key for an ID, but I find this limiting, and not always sufficient for the problem When you need to identify a specific job by multiple attributes like above, we must create a way to find these jobs (without loading every job into memory and looping over them to see if one matches our criteria).
How is this working?
The Struct that the ApiJob extends has a :token attribute. This token is based on the attributes passed (attr1, attr2, attr3) and is built when a new class extending ApiJob is instantiated.
The find_by_token class method simply searches the string representation of the job in the delayed_job queue for a match based on a token built using the same token class method.

Processing data before saving to database

I have decimal field in my DB. Users can input values in two formats: with comma or point (11,11 or 11.11).
But MySQL allows to save data only in 'point' format, so i want to process data before saving with regex like this:
sub(/,/,".")
How can i do it in Rails3?
If I understand you correctly, this could be done in the controller or the model. I might use the before_save callback in the model to achieve this in the following way:
class Item < ActiveRecord::Base
before_save :standardise_numbers
...
protected
# Called before this object is saved to the DB
def standardise_numbers
self.number.sub!(",", ".")
end
end
Where number is the attribute you're wanting to convert.
I assume you don't need to convert it back to comma representation to display to the user? If you do, you may want to look into the internationalisation API for Rails, Il8n. It handles this kind of stuff and more, so definitely worth looking into.
Alternative Solution (edit)
Based on your feedback, my above solution doesn't work since the number is already converted and the decimal part lost when it is passed into the model. A similar piece of code could be used in the controller to intercept and convert the number in the params hash itself:
class PostController < ActionController
before_filter :standardise_numbers, :only => [ :create, :update ]
def create
#post = Post.create(params[:post])
end
protected
# Intercepts the params hash
def standardise_numbers
params[:post][:number].sub!(",", ".")
end
end
This simplifies the create and update methods, allowing you to deal with the hash in the same way you normally would.
I played this it and found this:
Suppose what in form field number, user inputs value '12,13'.
Value from form go to PostController to 'create' method
class PostController < ApplicationController
def create
#post = Post.new(params[:post])
#on this step instance of Post model created, validated and filled with relevant values
#so #post.number == '12' #(decimal), it cuts ',13'
#we need to redefine #post.number
#post.number = params[:post][:number].gsub(/,/,'.').to_f # => 12.13
#and after that save the post
#post.save
end

Rails: modify form parameters before modifying the database

I'm working on a Rails app that sends data through a form. I want to modify some of the "parameters" of the form after the form sends, but before it is processed.
What I have right now
{"commit"=>"Create",
"authenticity_token"=>"0000000000000000000000000"
"page"=>{
"body"=>"TEST",
"link_attributes"=>[
{"action"=>"Foo"},
{"action"=>"Bar"},
{"action"=>"Test"},
{"action"=>"Blah"}
]
}
}
What I want
{"commit"=>"Create",
"authenticity_token"=>"0000000000000000000000000"
"page"=>{
"body"=>"TEST",
"link_attributes"=>[
{"action"=>"Foo",
"source_id"=>1},
{"action"=>"Bar",
"source_id"=>1},
{"action"=>"Test",
"source_id"=>1},
{"action"=>"Blah",
"source_id"=>1},
]
}
}
Is this feasible? Basically, I'm trying to submit two types of data at once ("page" and "link"), and assign the "source_id" of the "links" to the "id" of the "page."
Before it's submitted to the database you can write code in the controller that will take the parameters and append different information before saving. For example:
FooController < ApplicationController
def update
params[:page] ||= {}
params[:page][:link_attributes] ||= []
params[:page][:link_attriubtes].each { |h| h[:source_id] ||= '1' }
Page.create(params[:page])
end
end
Edit params before you use strong params
Ok, so (reviving this old question) I had a lot of trouble with this, I wanted to modify a param before it reached the model (and keep strong params). I finally figured it out, here's the basics:
def update
sanitize_my_stuff
#my_thing.update(my_things_params)
end
private
def sanitize_my_stuff
params[:my_thing][:my_nested_attributes][:foo] = "hello"
end
def my_things_params
params.permit(etc etc)
end
You should also probably look at callbacks, specifically before_validate (if you're using validations), before_save, or before_create.
It's hard to give you a specific example of how to use them without knowing how you're saving the data, but it would probably look very similar to the example that Gaius gave.

Resources