I'm trying to implement the Railscast 340 that demos how to use DataTables, which looks like an awesome gem for my project.
My model is different, of course; but the datatables class the Mr Bates builds (very quickly), in order to do server-side processing, is rather complicated to follow. I got the source code, and basically attempted to follow along. My view comes up with zero records (but there are > 10,000 records), but does not break.
However, here is what the error message output from the rails server says just before it stops:
NameError (undefined local variable or method `genotypes' for #<GenotypesDatatable:0xa9e852c>):
app/datatables/genotypes_datatable.rb:12:in `as_json'
app/controllers/genotypes_controller.rb:8:in `block (2 levels) in index'
app/controllers/genotypes_controller.rb:6:in `index'
Just before this, there appears to be this JSON error, which starts:
Started GET "/genotypes.json?sEcho=1&iColumns=8&sColumns=&iDisplayStart=0&iDisplayLength=10&mDataProp_0=...
The relevant part of the genotypes controller looks like this:
def index
respond_to do |format|
format.html
format.json { render json: GenotypesDatatable.new(view_context) }
end
end
And my genotypes model looks like:
class Genotype < ActiveRecord::Base
attr_accessible :allele1, :allele2, :run_date
belongs_to :gmarkers
belongs_to :gsamples
end
My datatables class is given below. This is from Mr Bates code, modified (most likely incorrectly) to replace his Products model with my Genotypes model:
class GenotypesDatatable
delegate :params, :h, :link_to, to: :#view
def initialize(view)
#view = view
end
def as_json(options = {})
{
sEcho: params[:sEcho].to_i,
iTotalRecords: Genotype.count,
iTotalDisplayRecords: genotypes.total_entries,
aaData: data
}
end
private
def data
genotypes.map do |genotype|
[
link_to(genotype.name, genotype),
h(genotype.category),
h(genotype.released_on.strftime("%B %e, %Y")),
genotype.run_date
]
end
end
def Genotypes
#Genotypes ||= fetch_Genotypes
end
def fetch_genotypes
genotypes = Genotype.order("#{sort_column} #{sort_direction}")
genotypes = genotypes.page(page).per_page(per_page)
if params[:sSearch].present?
genotypes = genotypes.where("name like :search or category like :search", search: "%#{params[:sSearch]}%")
end
genotypes
end
def page
params[:iDisplayStart].to_i/per_page + 1
end
def per_page
params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 10
end
def sort_column
columns = %w[gmarker gsample allele1 allele2 run_date]
columns[params[:iSortCol_0].to_i]
end
def sort_direction
params[:sSortDir_0] == "desc" ? "desc" : "asc"
end
end
Any hints on how to troubleshoot (or fix!) this error much appreciated! (Getting this working for my project would be awesome!)
TIA,
rixter
I'm not sure if this is it, but your class has a Genotype method with capital G, it should be all lowercase.
Related
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
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?
`
I'm working on a fairly simple site that allows users to choose recipe ingredients, their quantities and then shows them nutritional info based on their recipe and a large database.
Right now, I feel like I'm repeating myself a bit. I want to be able to make this "DRY" by having one method each in the Recipe and Recipe_Ingredient model that will do the same thing only accept the right parameter, which will be the type of nutrient.
Here is the relevant code in my view that currently calls two different methods (and will call more when extended to the other nutrients):
<ul>Calories <%= #recipe.total_calories %></ul>
<ul>Fat (grams) <%= #recipe.total_fat %></ul>
In my recipe model, I have methods that iterate over each of the ingredients in the recipe:
def total_calories
recipe_ingredients.to_a.sum { |i| i.total_calories }
end
def total_fat
recipe_ingredients.to_a.sum { |i| i.total_fat }
end
In the block, we call two separate methods that actually calculate the nutrients for each individual recipe ingredient:
def total_calories
ingredient.calories*ingredient.weight1*quantity/100
end
def total_fat
ingredient.fat*ingredient.weight1*quantity/100
end
This last piece is where we reference the database of ingredients. For context, here are the relationships:
class RecipeIngredient < ActiveRecord::Base
belongs_to :ingredient
belongs_to :recipe
class Recipe < ActiveRecord::Base
has_many :recipe_ingredients
Thanks in advance for any help.
Lev
The send method with a symbol parameter works well for that kind of DRY.
<ul>Calories <%= #recipe.total :calories %></ul>
<ul>Fat (grams) <%= #recipe.total :fat %></ul>
Recipe
def total(type)
recipe_ingredients.to_a.sum { |i| i.total type }
end
RecipeIngredient
def total(type)
ingredient.send(type) * ingredient.weight1 * quantity / 100
end
You could use meta programming to dynamically add the methods. Here is a start, you can get even more DRY than this.
class DynamicTotalMatch
attr_accessor :attribute
def initialize(method_sym)
if method_sym.to_s =~ /^total_of_(.*)$/
#attribute = $1.to_sym
end
end
def match?
#attribute != nil
end
end
Recipe
class Recipe
def self.method_missing(method_sym, *arguments, &block)
match = DynamicTotalMatch.new(method_sym)
if match.match?
define_dynamic_total(method_sym, match.attribute)
send(method_sym, arguments.first)
else
super
end
end
def self.respond_to?(method_sym, include_private = false)
if DynamicTotalMatch.new(method_sym).match?
true
else
super
end
end
protected
def self.define_dynamic_total(method, attribute)
class_eval <<-RUBY
def self.#{method}(#{attribute})
recipe_ingredients.to_a.sum { |i| i.send(attribute)
end
RUBY
end
end
RecipeIngredient
class RecipeIngredient
def self.method_missing(method_sym, *arguments, &block)
match = DynamicTotalMatch.new(method_sym)
if match.match?
define_dynamic_total(method_sym, match.attribute)
send(method_sym, arguments.first)
else
super
end
end
def self.respond_to?(method_sym, include_private = false)
if DynamicTotalMatch.new(method_sym).match?
true
else
super
end
end
protected
def self.define_dynamic_total(method, attribute)
class_eval <<-RUBY
def self.#{method}(#{attribute})
ingredient.send(attribute) * ingredient.weight1 * quantity / 100
end
RUBY
end
end
Example was copied from ActiveRecord and this page: http://technicalpickles.com/posts/using-method_missing-and-respond_to-to-create-dynamic-methods/
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
using ruby 1.9.2 and rails 3, i would like to limit the fields returned when a record is accessed as json or xml (the only two formats allowed).
this very useful post introduced me to respond_with and i found somewhere online that a nice way to blanket allow/deny some fields is to override as_json or to_xml for the class and set :only or :except to limit fields.
example:
class Widget < ActiveRecord::Base
def as_json(options={})
super(:except => [:created_at, :updated_at])
end
def to_xml(options={})
super(:except => [:created_at, :updated_at])
end
end
class WidgetsController < ApplicationController
respond_to :json, :xml
def index
respond_with(#widgets = Widgets.all)
end
def show
respond_with(#widget = Widget.find(params[:id]))
end
end
this is exactly what i am looking for and works for json, but for xml "index" (GET /widgets.xml) it responds with an empty Widget array. if i remove the to_xml override i get the expected results. am i doing something wrong, and/or why does the Widgets.to_xml override affect the Array.to_xml result?
i can work around this by using
respond_with(#widgets = Widgets.all, :except => [:created_at, :updated_at])
but do not feel that is a very DRY method.
In your to_xml method, do the following:
def to_xml(options={})
options.merge!(:except => [:created_at, :updated_at])
super(options)
end
That should fix you up.