Rails - how to add parameter to the "params.require(:model).permit" construction? - ruby-on-rails

I have following code in the controller:
def create
#company = Company.new(company_params)
....
end
private
def company_params
params.require(:company).permit(...list of columns...).merge(latlng: geocode)
end
I would need to save to the database the column called slug. The value of this column would be
slug = params[:company][:name].parameterize
The column slug is not mentioned in the permit list. How to add & save this information to the database?
Thanks

def blog_post_params
params.require(:post).permit(:title, :author, :published, :body)
end

Related

How do you create a nested object form in Rails?

I'm building a simple Rails-based travel app for a school project.
I want to make a form that enables the user to enter data such as the name of their Trip, two destinations, and each destination will have a location.
How can I do this with a triple nest form?
I'm very confused by the build method right now. Below is the def new method and the private methods in the controller.
class TripsController < ApplicationController
def new
#trip = Trip.new
Destination.all.each do |destination|
#trip.destinations.build.location
end
private
def set_trip
#trip = Trip.find(params[:id])
end
def trip_params
params.require(:trip).permit(:name, :start_date, :end_date,
:destination_1, :destination_2, :user_id, :destinations_attributes => [:name,
:locations_attributes => [:name]
]
)
Google pretty much answers this.
https://levelup.gitconnected.com/rails-nested-forms-in-three-steps-5580f0ad0e
https://guides.rubyonrails.org/form_helpers.html
You are looking for fields_for

Ruby on Rails - how to handle belongs_to multiple on create

I'm trying to create a Comment that belongs_to multiple Models but I'm not sure on what the best practice is to assign it to both.
class Comment < ApplicationRecord
validates :message, :presence => true
belongs_to :user
belongs_to :discussion
end
When creating a Comment within a Discussion, I run into the error "Discussion must exist".
I'm using Devise so I am using current_user and building a Comment to align it with them, but I'm not sure on exactly how to point it to both. This seems to work, but seems very hacky.
def create
#comment = current_user.comments.build(comment_params)
#comment.discussion_id = params[:discussion_id]
...
end
What's the best practice?
I would add the :discussion_id to the comment_params method:
def create
#comment = current_user.comments.build(comment_params)
...
end
private
def comment_params
params.require(:comment)
.permit(...)
.merge(discussion_id: params.require(:discussion_id))
end
Or you could use build with a block:
def create
#comment = current_user.comments.build(comment_params) { |comment|
comment.discussion_id = params[:discussion_id]
}
# ...
end
No matter how you go about it, you do need to either set discussion_id or get it to be present in comment_params.
Does this look less hacky to you?
def create
comment_params.merge(discussion_id: params[:discussion_id])
#comment = current_user.comments.build(comment_params)
...
end
You can pass additional params in the block
params = ActionController::Parameters.new({
comment: {
name: 'Francesco',
body: 'Text'
}
})
comment_params = params.require(:comment).permit(:name, :body)
def create
#comment = current_user.comments.build(comment_params) do |comment|
comment.discussion_id = params[:discussion_id]
end
end
There are several things you need to keep in mind when you create an entity which is associated to more than one entity.
With respect to your question regarding to best practices. I would like to emphasise the usage of an interactor here:
# xx_controller.rb
def create
success = CreateComment.call(params)
if success
....
else
....
end
end
# ../../create_comment.rb
# pseudocode
class CreateComment
def call(params)
validate your params # check if nothing is missing, the discussion exists and you have a user
build_and_save_your_comment
return_your_result_object # return a tuple or a result or whatever you need in order to handle errors and success cases...
end
end
With this approach you keep the readability within your controller and you can focus on what matters to the comment creation in a dedicated place.

Updating rails model's tags with null id issue

I have a rails modle like this
class Article < ActiveRecord::Base
has_many :tags
def all_tags=(keys)
self.tags = keys.split(',').map do |key|
Tag.where(article_id: id, key: key.strip).first_or_create!
end
end
def all_tags
tags.map(&:key).join(', ')
end
end
Basically what I want to do is to allow user set tags on it, it would look like this in controller
def create
#article = article(article_params)
if # article.persisted?
redirect_to article s_path
else
render 'new'
end
end
However, as in def all_tags=(keys), article.id is not present yet. So I got error like this
PG::NotNullViolation: ERROR: null value in column "article_id" violates not-null constraint
Here is the question, how to ensure article is persisted before all_tags got update?
When you use Model.where(conditions).first_or_create!, Active Record tries to insert into database a new record for Model if can't found one.
In your case, since you are setting the attributes before save the model, then the create launch an exception.
To fix the issue just change first_or_create with first_or_initialize
Yes you can do validation something like
validates :your_method, on: :update
def your_method
{with logic not be null}
end

Can't save record attributes to the database

class Contact < ActiveRecord::Base
attr_accessor :email
def initialize(data)
data.each { |k, v| send("#{k}=", v) }
end
end
In rails console
Contact.create!({"email"=>"foo#gmail.com"})
The record saved to the database has email as nil
Update:
The data is being passed in is JSON. I am getting all the data from the JSON and trying to save that into the database.
Did you try:
Contact.create!(email: "foo#gmail.com")
The email as a :symbol and no curly brackets?
Also, why are you initializing in your model?
With Mohamed El Mahallaway, I think your code setup could be improved (to negate initializing your model). I think you'll be better using the strong_params functionality of Rails:
#app/controllers/contacts_controller.rb
def new
#contact = Contact.new
end
def create
#contact = Contact.new(contact_params)
#contact.email = "foo#gmail.com"
#contact.save
end
private
def contact_params
params.require(:contact).permit(:email, :other, :params)
end
I may have miscalculated your competency with Rails, but this is the "Rails" way to save the correct data to your model :) You may to have a before_save method in your model to use the email attribute, considering it's a virtual attribute

Casting ActiveModel attribute on return

In my Rails 3.1.1 project I have an ActiveModel that talks to API (ripped from Paul Dix's book, shortened for readability):
class Job
include ActiveModel::Validations
include ActiveModel::Serializers::JSON
ATTRIBUTES = [ :id,
:title,
:description,
:company_id ]
attr_accessor *ATTRIBUTES
validates_presence_of :title, :description
validates_numericality_of :company_id, :id
def initialize(attributes = {})
self.attributes = attributes
end
def attributes
ATTRIBUTES.inject(
ActiveSupport::HashWithIndifferentAccess.new
) do |result, key|
result[key] = read_attribute_for_validation(key)
result
end
end
def attributes=(attrs)
attrs.each_pair {|k, v| send("#{k}=", v)}
end
def read_attribute_for_validation(key)
send(key)
end
# More method definitions...
end
I instantiate #job in my controller, new action (company_id is a segnment key in the route: /companies/:company_id/jobs/new) like this:
#job = Job.new(company_id: params[:company_id])
Then, using CanCan, I check user's permissions to create to create a job. Basically, CanCan checks if current_user's company_id attribute matches job's company_id. This check fails because #job.company_id is returned as String.
Certainly, I can use params[:company_id].to_i while instantiating the object, but this seems like a workaround that I would have to repeat later.
Question: is there a way to make my Job ActiveModel more "type-aware" and make it return int for #job.company_id call?
I googled around, checked activemodel source code, but doesn't seem to find an answer. Any help is greatly appreciated.
Update
I was thinking more of something like schema block for ActiveModel, just like the one in ActiveResource.
attr_accessor *ATTRIBUTES
create a method like this:
def company_id
#company_id
end
You can just override that with
def company_id
#company_id.to_i
end
Answering my own question....
mosch's answer suggested to override the getter for company_id in my ActiveModel. However, I would have to repeat this for all of _id attributes in the model. Therefore, I decided to cast all of the '_id' attributes to integers while initializing the object. As follows:
def attributes=(attrs)
attrs.each_pair do |k, v|
if "#{k}".include? "_id"
send("#{k}=", v.to_i)
else
send("#{k}=", v)
end
end
end
I'm assuming your Company has_many => :jobs? If so, you could try
def new
#company = Company.find(params[:company_id])
#job = #company.jobs.new
end

Resources