Lets say I had code in a controller that did not use Strong Parameters
Model.create name: params[:name], alias_id: params[:foreign_id]
Is it possible for me to use Strong Parameters here?
I cannot do
Model.create params.require(:name, :foreign_id)
Because foreign_id is not a param
I cannot do
Model.create params.require(:name, :alias_id)
Because alias_id is not on the model.
Basically, I want to know if you can alias paramater keys when using Strong Parameters.
Usually if I want to map params in this way (usually due to some external API) I use the alias_attribute method in ActiveRecord
So if I have params that come to me as {name: "bob", alias_id: 1234} and ideally I would want to have {name: "bob", foreign_id: 1234}
I declare the following in my ActiveRecord model
class MyModel < ActiveRecord::Base
alias_attribute :alias_id, :foreign_id
end
Now my model will always recognise alias_id as mapping to foreign_id in any context and I can push in params.require(:name, :alias_id) and my model will recognise it and process it as required without looping over attributes in my controller.
This is also simpler if you want to do it on other models as well.
I got the functionality I wanted with the following piece of code. I don't think Strong Parameters can do what I need, especially as require() cannot take multiple parameters.
By putting this in my ApplicationController or a module it inherits
#
# Pass in a list of param keys to pull out of the params object
# Alias param keys to another key by specifiying a mapping with a Hash
# eg. filter_params :foo, :bar, {:choo => :zoo}
#
def filter_params(*param_list)
filtered_params = {}
param_list.each do |param|
if param.is_a? Hash
param.each {|key, value| filtered_params[value] = params[key]}
else
filtered_params[param] = params[param]
end
end
filtered_params
end
I can now say
Model.create filter_params(:name, {foreign_id: :alias_id})
Related
I'd like to know what the best way to ensure a user supplied parameter is downcased and stripped in all situations.
I would like to achieve the following:
Guarantee that the attribute will not be saved to the DB unless stripped/downcased
Queries against the db should always downcase/strip the attribute
Validations are run against a downcased/stripped version of user supplied params
Models return downcase/stripped attribute (which shouldn't be a problem given item #1)
you need to write a before_save callback method, within which you downcase and strip the attributes set by the user.
For eg:
class User < ActiveRecord::Base
before_save :format_values
def format_values
self.name = self.name.downcase
end
end
EDIT
I had missed your 3rd point about validations. So if you need to also run validations on these values. You'd need to use the before_validation callback instead.
class User < ActiveRecord::Base
before_validation :format_values
def format_values
self.name = self.name.strip.downcase if name
end
end
Updated the answer based on the comment.
No need to get fancy with callbacks (don't use callbacks, anyway). Just override the setter for your attribute.
class MyModel
def some_attribute=(value)
value = value.strip.downcase if value
write_attribute(:some_attribute, value)
end
end
You would do that in a before_validation callback:
# in your model
before_validation :normalize_attribute
private
def normalize_attribute
# change `attribute` to your actual attribute's name
self.attribute = attribute.strip.downcase if attribute
end
Or you could do that with a custom setter:
# change `attribute` to your actual attribute's name
def attribute=(value)
write_attribute(:attribute, value.strip.downcase) if value
end
The first option will sanitize the attribute's value every time the object is saved, even if the value has not changed. This might be helpful if you introduce this sanitize method when records in the database already exist, because this allow to sanitize all existing record with just one line of code in the Rails console: Model.find_each(&:save). The second option will only sanitize values when they are set. This is a bit more performant.
I both cases I suggest to check for if attribute otherwise you might call strip.downcase on nil values what would lead to an exception.
Suppose there is a Rails model with a custom setter/accessor and a uniqueness constraint on the name column:
class Person < ActiveRecord::Base
validates :name, presence: true, uniqueness: true
def name=(name)
# Example transformation only.
# Could be substituted for a more complex operation/transformation.
title_cased = name.titleize
self[:name] = title_cased
end
end
Now, consider the following:
Person.create! name: "John Citizen"
Person.find_or_create_by! name: "john citizen" # Error: validation fails
The find operation will not find any results, since there are no entries that match "john citizen". Then, the create! operation will throw an error as there is already an existing entry "John Citizen" (create! creates a new record and raises an exception if the validation fails).
How do you elegantly prevent such errors from occurring? For loose coupling and encapsulation purposes, is it possible to not transform names (to titlecase, in this case) before I perform operations like find_or_create_by! or other operations like find_by?
EDIT:
As #harimohanraj alludes to, the issue seems to be around equivalence. Should the model transparently deal with the understanding/translating input to its boiled-down, canonical state. Or should this be the responsibility of consumers of the class/model?
Also, is active record callbacks a recommended approach to this kind of scenario?
If you have defined a custom setter method, the implicit decision that you have made is: values for the name attribute, no matter what form they come in (eg. a user's input in a text field), should be handled in titleized form in your DB. If that's the case, then it makes sense that find_or_create_by! name: 'john citizen' fails! In other words, your custom setter method represents your decision that "John Citizen" and "john citizen" are one and the same.
If you find yourself wanting to store John Citizen and john citizen in your DB, then I would revisit your decision to create a custom setter method. One cool way to achieve "loose coupling" is to put all of the logic that sanitizes data (ex. data from a user filling out a form) into a separate Ruby object.
There isn't much context in the question, so here is a bit of an abstract example to demonstrate what I mean.
# A class to house the logic of sanitizing your parameters
class PersonParamsSanitizer
# It is initialized with dirty user parameters
def initialize(params)
#params = params
end
# It spits out neat, titleized params
def sanitized_params
{
name: #params[:name].titleize
}
end
end
class PersonController < ApplicationController
def create
# Use your sanitizer object to convert dirty user parameters into neat
# titleized params for your new perons
sanitized_params = UserParamsSanitizer.new(params).sanitized_params
person = Person.new(sanitized_params)
if person.save
redirect_to person
else
render :new
end
end
end
This way, you don't override the setter method in your User model, and are free to use find_or_create_by! fearlessly if you so choose!
You can set a validation to be case-insensitive by using:
class Person < ActiveRecord::Base
validates :name,
presence: true,
uniqueness: { case_sensitive: false }
end
However you also need a case-insensitive database index backing it since just using a validation in Rails will lead to race conditions. How to achieve that depends on the RBDMS.
Which leaves the issue of querying. The classic way of performing a intensive search is by WHERE LOWER(name) = LOWER(?). Although Postgres lets you use WHERE ILIKE name = ?.
If you want to encapsulate this into the model which is a good idea you would create a scope:
class Person
scope :find_by_name, lambda{ |name| where('LOWER(name) = LOWER(?)', name) }
end
However, you cannot use .find_or_create_by! in this case as the query not just a hash. Instead you would call .first_or_create.
Person.find_by_name("John Citizen").first_or_create(attrs)
see also
PostgreSQL: How to make "case-insensitive" query
The problem is the find_or_create_by and similar methods are already not tansforming the name... as you say there is no record "john citizen" but to work properly you'd need to titleize it for the find_or_create_by, find_or_create_by!, or find_by
(you don't need this solution for find as that only retrieves record by primary key)
so...
def self.find_or_create_by(options)
super(rectify_options(options))
end
def self.find_or_create_by!(options)
super(rectify_options(options))
end
def self.find_by(options)
super(rectify_options(options))
end
private
def self.rectify_options(options)
options[:name] = (new.name = options[:name]) if options[:name]
options
end
I have to work with an legacy app and have to rewrite the old (PHP base)rest api. In the old api, when an attribute was null, it became an empty string.
Rails however just returns a null, which breaks the app. Rewriting the app is not the solution (even it this would be the cleanest way). Also in the old api, the other values are strings to (integers, booleans, numbers). So I was wondering, how can I do a to_s on every attribute witihout overriding every attribute ofcourse. I'm using active model serializer.
A little metaprogramming should help:
class MySerializer < ActiveModel::Serializer
[:id, :attr1, :attr2, :attr2].each do |attr|
# Tell serializer its an attribute
attribute attr
# Define a method with the same name as the attribute that calls the
# underlying object and to_s on the result
define_method attr do
object.send(attr).to_s
end
end
end
I'm playing around with Hstore for the first time in a rails4 app, and I am using javascript in a form to build out dynamic form fields for the hstore column (:schema)
In rails 4 I dont need to add any setter/getter method in my model, correct?
In my form i am building dynamic input fields and allowing the user to set the key/value pairs. Much like the Hstore Heroku Demo App
So basically my form would have inputs like
input name="app[schema][dynamic_key1]" value="whatever value"
input name="app[schema][dynamic_key2]" value="whatever value2"
In my App Controller:
def app_params
params.require(:app).permit(:name, :title, :schema )
end
However, when i create a new App record, my schema hstore values are not saving. I saw some things about making the strong param for :schema => [] but that still does not work.
Since I do not know what these values will be, i cant setup store_accessors for these like I have seen in a lot of examples.
found this here: http://guides.rubyonrails.org/action_controller_overview.html#more-examples
and in my controller I used:
def app_params
params.require(:app).permit(:name, :title).tap do |whitelisted|
whitelisted[:schema] = params[:app][:schema]
end
end
I think Rails must have simplified this in recent versions (current as of 5.2.3 at least)... and much cleaner/easier:
params.require(:parent).permit(:name, :whatever, data: {})
This will allow and store any/all attributes of data into an hstore field. An example of POSTing or PUTing a data nested attribute via HTML:
<input type="text" name="parent[data][your_super_custom_nested_data] />`
4th example down: https://guides.rubyonrails.org/action_controller_overview.html#more-examples
Here's a way that also allows deleting of the hstore keys by submitting empty
parameters.
In your ApplicationController add this method:
# Returns the hstore keys to be whitelisted.
#
# #param key [Symbol] the name of the hstore field
# #param params [Hash] the parameters for the hstore field
#
# #return [{Symbol => Array<Symbol>}, Symbol]
def permit_hstore_params(key, hstore_params)
keys = hstore_params.try(:keys)
# Return key if params are empty,
# this allows the hstore key to be removed.
return key if keys.blank?
# Otherwise, return the keys to be whitelisted
{ key => keys }
end
Example:
class DynamicRecord < ActiveRecord::Base
store_accessor :metadata
end
class DynamicRecordController < ApplicationController
# ...
def dynamic_model_params
params
.require(:dynamic_model)
.permit(:name, permit_hstore_params(:metadata, params[:dynamic_model][:metadata]))
end
end
How does attr_accessor works in ActiveResource?
class User < ActiveResource::Base
attr_accessor :name
end
How its different from attr_accessor in ActiveRecord?
attr_accessor is built into Ruby, not rails. You may be confusing it with attr_accessible, which is part of ActiveRecord. Here's the difference:
attr_accessor
Take a class:
class Dog
attr_accessor :first_name, :last_name
def initialize(first_name, last_name)
self.first_name = first_name
self.last_name = last_name
end
end
attr_accessor creates a property and creates methods that allow it to be readable and writeable. Therefore, the above class would allow you to do this:
my_dog = Dog.new('Rex', 'Thomas')
puts my_dog.first_name #=> "Rex"
my_dog.first_name = "Baxter"
puts my_dog.first_name #=> "Baxter"
It creates two methods, one for setting the value and one for reading it. If you only want to read or write, then you can use attr_reader and attr_writer respectively.
attr_accessible
This is an ActiveRecord specific thing that looks similar to attr_accessor. However, it behaves very differently. It specifies which fields are allowed to be mass-assigned. For example:
class User
attr_accessible :name, :email
end
Mass assignment comes from passing the hash of POST parameters into the new or create action of a Rails controller. The values of the hash are then assigned to the user being created, e.g.:
def create
# params[:user] contains { name: "Example", email: "..."}
User.create(params[:user])
#...
end
For the sake of security, attr_accessible has to be used to specify which fields are allowed to be mass-assigned. Otherwise, if the user had an admin flag, someone could just post admin: true as data to your app, and make themselves an admin.
In summary
attr_accessor is a helper method for Ruby classes, whereas attr_accessible is an ActiveRecord thing for rails, to tighten up security.
You don't need to have attr_accessor to work with ActiveResource.
The base model (ActiveResource::Base) contains the #attributes hash in which you can 'dump' properties as you wish. (you should be careful though on what params you allow)
The way it does this, is by handling the method_missing? method.
You can take a look here
If you define attr_accessor, what ruby does is that it creates a setter and a getter method, so it will break the method_missing functionality since it will never get to execute that code.
If you still want to use attr_accessor, you should create a Concern something like this:
module Attributes
extend ActiveSupport::Concern
module ClassMethods
def attr_accessor(*attribs)
attribs.each do |a|
define_method(a) do
#attributes[a]
end
define_method("#{a}=") do |val|
#attributes[a] = val
end
end
end
end
end