strong param method expects to `define method_name` as `controller name` - ruby-on-rails-5

rails (5.2.2.1)
ruby 2.5.0p0
Parent controller of Country, State, City
class LocalityController < ApplicationController
def create
locality = model_name.new(locality_master_params)
respond_to do |format|
if locality.save
format.html { redirect_to locality, notice: 'Record was successfully created.' }
else
format.html { render :new }
end
end
end
private
def model_name
"#{controller_name.titleize.delete(' ').singularize}".constantize
end
def locality_params
#locality_params = %i|name code status|
end
def locality_master_params
params.require("#{controller_name.singularize}".to_sym).permit(locality_params)
end
end
State controller
class StateMastersController < LocalityController
alias_method :state_master_params, :locality_master_params
private
def locality_params
#locality_params = %i|name code status country_code|
end
end
Expectation: country-state-city controllers should be inherited from one controller and manage same templates, methods for all those controllers to DRY.
This code works fine as per the expectation.
Issue: after removing below code(as it is unnecessary):
alias_method :state_master_params, :locality_master_params
it gives error as:
ActiveModel::ForbiddenAttributesError
I've added alias_method to prevent above error.
Getting same error in other controllers too: country-city controllers.
Is there any convention to define method as state_master_params for state_master_controller?
`

Related

How can I call a create method of a controller in another controller in Ruby on Rails?

I have two controllers one is Payments and one is Transactions, I need to call the create method of transactions inside the create method of payments. So that each time I create a payment a transaction is automatically created. How should I approach this?
module Api
class PaymentsController < ApplicationController
before_action :set_payment, only: %i[ show update destroy ]
def index
#payments = Payment.all
render json: #payments
end
def show
render json: #payment
end
def create
#payment = Payment.new(payment_params)
if #payment.save
render json: 'Payment Is Made sucessfully'.to_json, status: :ok
else
render json: #payment.errors, status: :unprocessable_entity
end
end
private
def payment_params
params.permit(:currency, :amount, :payment_type, :payment_address)
end
end
end
module Api
class TransactionsController < ApplicationController
before_action :set_transaction, only: %i[show update destroy]
def index
#transactions = Transaction.all
render json: #transactions
end
def show
render json: #transaction
end
# POST /transactions
def create
#transaction = Transaction.new(transaction_params)
if #transaction.save
render json: #transaction, status: :created, location: #transaction
else
render json: #transaction.errors,status::unprocessable_entity
end
end
private
def transaction_params
params.permit(:transaction_type, :bank_name, :debit, :credit, :total_amount)
end
end
end
First to note, MVC is just a convention or good way to organize your code.
You can simply do:
def create
#transaction = Transaction.new(transaction_params)
# You'll have to figure out how to get the params in here - maybe they are all derived from the transaction?
#payment = Payment.new()
#payment.save
# handle payment error here too like below
if #transaction.save
render json: #transaction, status: :created, location: #transaction
else
render json: #transaction.errors,status::unprocessable_entity
end
end
However, this doesn't present itself well towards reusable code.
We have 2 options - put it into the model or introduce a service.
In the transaction model, we can create a function like:
class Transaction
...
def self.create_with_payment(params)
Transaction.create(params)
Payment.create(params)
# do whatever here to create and associate these
end
or we can introduce a service object:
class TransactionPaymentCreator
def call(params)
Transaction.create(params)
Payment.create(params)
end
end
You can then call this in your controller:
def create
service = TransactionPaymentCreator.new
service.call
end
I'm leaving out a lot of detail like how to set these things up - but I hope to convey to you the general details - you have 3 options on how to make this work.
Here is a good article and resource on reading more for service objects if you decide to go that route:
https://www.honeybadger.io/blog/refactor-ruby-rails-service-object/
My approach would be to make a callback on your Payment model:
# payment.rb
after_create :create_transaction
def create_transaction
Transaction.create(payment_id: id) # or whatever params you need in the transaction
end

Undefined method permit

I am practicing the posts in rails guide. In comments controller I write like this but it comes to error
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:commenter, :body))
redirect_to post_path(#post)
end
end
I'd recommend to follow up this guide
This should work:
class CommentsController < ApplicationController
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.create(comment_params)
redirect_to article_path(#article)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end

How to destroy an instance variable of one class within the controller of another class

I'm doing the chapter exercises of Agile Web Development with Rails. It's a store with products which you can add to a cart. A product inside a cart is called a LineItem. You can delete individual line items inside the cart. When the last line item is deleted I want the entire cart to be destroyed. The code I have doesn't work:
CartsController
def destroy
#cart = current_cart
#cart.destroy
session[:cart_id] = nil
respond_to do |format|
format.html { redirect_to store_path }
format.json { head :no_content }
end
end
LineItemsController
def destroy
#cart = current_cart
#line_item = LineItem.find(params[:id])
quantity = #line_item.quantity
if quantity > 1
quantity -= 1
#line_item.update_attribute(:quantity, quantity)
else
#line_item.destroy
end
respond_to do |format|
format.html {
if #cart.line_items.empty?
#cart.destroy
else
redirect_to #cart
end
}
format.json { head :no_content }
end
end
This produces the following error:
Template is missing
Missing template line_items/destroy, application/destroy with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :coffee]}. Searched in: * "/home/mike/Projects/depot/app/views"
When the cart is destroyed I want to end up at store_path.
thanks,
mike
See the addition (as a comment):
if #cart.line_items.empty?
#cart.destroy
# redirect_to store_path
else

Refactor this controller?

class ArticlesController < ApplicationController
def index
#articles = Article.by_popularity
if params[:category] == 'popular'
#articles = #articles.by_popularity
end
if params[:category] == 'recent'
#articles = #articles.by_recent
end
if params[:category] == 'local'
index_by_local and return
end
if params[:genre]
index_by_genre and return
end
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #articles }
end
end
def index_by_local
# 10 lines of code here
render :template => 'articles/index_by_local'
end
def index_by_genre
# ANOTHER 10 lines of code here
render :template => 'articles/index_by_genre'
end
end
As you can see from above. My controller is not exactly thin. What its doing is, depending on the params that were passed, it interacts with the model to filter out records.
And if params[:local] or params[:genre] was passed. Then it calls its own methods respectively (def index_by_local and def index_by_genre) to do further processing. These methods also load their own template, instead of index.html.erb.
Is this quite typical for a controller to look? Or should I be refactoring this somehow?
We can move the first few lines into the model(article.rb):
def get_by_category(category)
# Return articles based on the category.
end
In this way we can completely test the article fetching logic using unit tests.
In general move all the code related to fetching records inside model.
Controllers in general
should authorize the user
get records using the params and assign them to instance variables
[These must typically be function
calls to model]
Render or redirect
I would define scopes for each of the collections you want to use.
class Article < ActiveRecord::Base
...
scope :popular, where("articles.popular = ?", true) # or whatever you need
scope :recent, where(...)
scope :by_genre, where(...)
scope :local, where(...)
...
def self.filtered(filter)
case filter
when 'popular'
Article.popular, 'articles/index'
when 'recent'
Article.recent, 'articles/index'
when 'genre'
Article.by_genre, 'articles/index_by_genre'
when 'local'
Article.local, 'articles/index_by_local'
else
raise "Unknown Filter"
end
end
end
Then in your controller action, something like this:
def index
#articles, template = Article.filtered(params[:category] || params[:genre])
respond_to do |format|
format.html { render :template => template }
format.xml { render :xml => #articles }
end
end

Rails - Capitalize Article Tags and Titles

Am trying to find a way of capitalizing the 1st letter of all Titles and Tags when a user submits an article. I can use the capitalize method, but where do I add it to the controller code blocks for it to work?
Thx
controllers/articles_controller:
def new
#article = Article.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #article }
end
end
controllers/tags_controller:
class TagsController < ApplicationController
def show
#tag = Tag.find(params[:id])
#articles = #tag.articles
end
end
models/article:
def tag_names
#tag_names || tags.map(&:name).join(' ')
end
private
def assign_tags
if #tag_names
self.tags = #tag_names.split(/\,/).map do |name|
Tag.find_or_create_by_name(name)
end
end
...
Where do you plan to capitalize it? before saving in the database? or when you're showing it to the user?
There are two ways to this:
Use rail's titleize function or capitalize
or do it using CSS with:
<p class="tag">im a tag</p>
#CSS
.tag {
text-transform:capitalize;
}
I would do something like this to force them to be capitalized before saving.
class Article < ActiveRecord::Base
def title=(title)
write_attribute(:title, title.titleize)
end
private
def assign_tags
if #tag_names
self.tags = #tag_names.split(/\,/).map do |name|
Tag.find_or_create_by_name(name.capitalize)
end
end
end
end
Try to use .capitalize
e.g. "title".capitalize will make "Title"
As Francis said, use .capitalize in your controller
I use this
#article= Article.new(params[:article])
#article.name = #article.title.capitalize
#article.save