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)
Related
In my application I have a number of pages where I need to display a list of people and allow the user to filter them with a form. And these pages are often similar looking. The filters share parts but still not the same.
I'm wondering how can I avoid repeating almost the same code for different controllers? I tried scopes but I still need to parse parameters and populate form in a view anyway.
Thanks!
Disclaimer: author of https://github.com/dubadub/filtered is here.
ActiveRecord offers a merge method for relations. It intersects two query parts which allows breaking query logic into parts.
Based on that idea I created a gem https://github.com/dubadub/filtered.
In your case it could be something like:
# app/controllers/people_controller.rb
class PeopleController < ApplicationController
before_action :set_filter
def index
#people = People.all.merge(#filter)
end
private
def set_filter
#filter = PersonFilter.new(filter_params)
end
def filter_params
params.fetch(:filter, {}).permit(:age, :active, :sorting)
end
end
# app/filters/person_filter.rb
class PersonFilter < ApplicationFilter
field :age
field :active do |active|
-> { joins(:memberships).merge(Membership.where(active: active)) }
end
field :sorting do |value|
order_by, direction = value.values_at("order", "direction")
case order_by
when "name"
-> { order(name: direction) }
when "age"
-> { order(age: direction) }
else
raise "Incorrect Filter Value"
end
end
end
# app/views/people/index.slim
= form_for(#filter, url: search_path, method: "GET", as: :filter) do |f|
.fields
span Age
= f.select :age, (18..90).map { |a| [ a, a ] }
.fields
span Active
= f.check_box :active
.fields
span Sorting
span Name
= f.radio_button :sorting, "name asc"
= f.radio_button :sorting, "name desc"
span Age
= f.radio_button :sorting, "age asc"
= f.radio_button :sorting, "age desc"
.actions
= f.submit "Filter"
Hope it helps!
Have you had a look at query objects?
https://mkdev.me/en/posts/how-to-use-query-objects-to-refactor-rails-sql-queries
They allow you to reuse the code in many places, you'd be able to simply pass the params.permit(...) and get get AR output.
# app/queries/user_query.rb
class UserQuery
attr_accessor :initial_scope
def initialize(scoped = User.all)
#initial_scope = initial_scope
end
def call(params) # is what you pass from your controller
scoped = by_email(#initial_scope, params[:email]
scoped = by_phone(scoped, params[:phone]
# ...
scoped
end
def by_email(scoped, email = nil)
email ? where(email: email) : scoped
end
def by_phone(scoped, phone = nil)
phone ? where(phone: phone) : scoped
end
end
# users_controller.rb
class UsersController < ApplicationController
def index
#users = UserQuery.new(User.all)
.call(params.permit(:email, :phone))
.order(id: :desc)
.limit(100)
end
end
# some other controller
class RandomController < ApplicationController
def index
#users = UserQuery.new(User.where(status: 1))
.call(params.permit(:email))
.limit(1)
end
end
You can probably refactor this example to reduce the upfront investment into writing these queries for richer objects, do post here if you come up with alternatives for so that others can learn how to use query objects.
I'm receiving a JSON object and nested array via a Rails 4 api with params like so:
{
"token" => "123"
"lessons" => [
{
"token_id" => "j12l3n123",
"attr_1" => "hello",
"attr_2" = "is it me you're looking for"
},
{
"token_id" => "j12l",
"attr_1" => "Nope",
"attr_3" = "You're not."
}
]
}
And I have a controller like so:
def update_all
#fetch collection with one db hit
token_ids = params[:lessons].map{|l| l[:token_id]}
#lessons = Lesson.where(token_id: token_ids)
params[:lessons].each do |l|
lesson = #lessons.detect { |lesson| lesson.token_id == l[:token_id] }
# How do I update the record with strong params?
lesson.update_attributes(lesson_params)
end
end
private
def lesson_params
params.permit(
:attr_1,
:attr_2,
:attr_3
)
end
How do i update each record with the right object in the array, and use strong parameters to do so?
def update_all
lesson_params.each do |l|
lesson = Lesson.where(token_id: l[:token_id]).first
lesson.update_attributes(l)
end
end
private
def lesson_params
params.require(:lessons).map do |l|
ActionController::Parameters.new(l.to_hash).permit(
:attr_1,
:attr_2,
:attr_3
)
end
end
def lesson_params
params.permit(:token, lessons: [:token_id, :attr_1, :attr_2, :attr_3 ])
end
in Controller something like following
def update_all
lesson_params[:lessons].each do |lesson_param|
lesson = Lesson.find(lesson_param[:token_id])
lesson.update_attributes(lesson_param)
end
end
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
I wrote a form object to populate an Order, Billing, and Shipping Address objects. The populate method looks pretty verbose. Since the form fields don't correspond to Address attributes directly, I'm forced to manually assign them. For example:
shipping_address.name = params[:shipping_name]
billing_address.name = params[:billing_name]
Here's the object. Note that I snipped most address fields and validations, and some other code, for brevity. But this should give you an idea. Take note of the populate method:
class OrderForm
attr_accessor :params
delegate :email, :bill_to_shipping_address, to: :order
delegate :name, :street, to: :shipping_address, prefix: :shipping
delegate :name, :street, to: :billing_address, prefix: :billing
validates :shipping_name, presence: true
validates :billing_name, presence: true, unless: -> { bill_to_shipping_address }
def initialize(item, params = nil, customer = nil)
#item, #params, #customer = item, params, customer
end
def submit
populate
# snip
end
def order
#order ||= #item.build_order do |order|
order.customer = #customer if #customer
end
end
def shipping_address
#shipping_address ||= order.build_shipping_address
end
def billing_address
#billing_address ||= order.build_billing_address
end
def populate
order.email = params[:email]
shipping_address.name = params[:shipping_name]
shipping_address.street = params[:shipping_street]
# Repeat for city, state, post, code, etc...
if order.bill_to_shipping_address?
billing_address.name = params[:shipping_name]
billing_address.street = params[:shipping_street]
# Repeat for city, state, post, code, etc...
else
billing_address.name = params[:billing_name]
billing_address.street = params[:billing_street]
# Repeat for city, state, post, code, etc...
end
end
end
Here's the controller code:
def new
#order_form = OrderForm.new(#item)
end
def create
#order_form = OrderForm.new(#item, params[:order], current_user)
if #order_form.submit
# handle payment
else
render 'new'
end
end
Noe I am not interested in accepts_nested_attributes_for, which presents several problems, hence why I wrote the form object.
def populate
order.email = params[:email]
shipping_params = %i[shipping_name shipping_street]
billing_params = order.bill_to_shipping_address? ?
shipping_params : %i[billing_name billing_street]
[[shipping_address, shipping_params], [billing_address, billing_params]]
.each{|a, p|
a.name, a.street = params.at(*p)
}
end
How about
class Order < ActiveRecord::Base
has_one :shipping_address, class_name: 'Address'
has_one :billing_address, class_name: 'Address'
accepts_nested_attributes_for :shipping_address, :billing_address
before_save :clone_shipping_address_into_billing_address, if: [check if billing address is blank]
Then when you set up the form, you can have fields_for the two Address objects, and side step the populate method entirely.
A possible fix would be to use a variable for retrieving those matching params, like so:
def populate
order.email = params[:email]
shipping_address.name = params[:shipping_name]
shipping_address.street = params[:shipping_street]
# etc...
#set a default state
shipping_or_billing = "shipping_"
#or use a ternary here...
shipping_or_billing = "billing_" if order.bill_to_shipping_address?
billing_address.name = params["shipping_or_billing" + "name"]
billing_address.street = params["shipping_or_billing" + "street"]
...
end
Your address classes should probably have a method that would set the values for all the address properties from a hash that it would receive as an argument.
That way your populate method would only check for order.bill_to_shipping_address? and them pass the correct dictionary to the method I'm suggesting.
That method on the other hand, would just assign the values from the hash to the correct properties, without the need for a conditional check.
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