I have custom field in form_for
check_box_tag "files_to_delete[]", :attach.id
Permitted params:
params.require(:post).permit( ... , :files_to_delete => [])
I have before_action :some_method in my model, in which i want to have access to the files_to_delete:
def some_method
files_to_delete.each do |attach|
attach.clear
end
end
But i get:
undefined local variable or method `files_to_delete' for #<Post:0x007f5c4cb51ad0>
Your model needs a setter and getter for files_to_delete. Add the following line to your model:
attr_accessor :files_to_delete
Related
I am trying to implement my own validations in Ruby for practice.
Here is a class Item that has 2 validations, which I need to implement in the BaseClass:
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
def initialize(attributes = {})
#price = attributes[:price]
#name = attributes[:name]
end
validates_presence_of :name
validates_numericality_of :price
end
My problem is: the validations validates_presence_of, and validates_numericality_of will be class methods. How can I access the instance object to validate the name, and price data within these class methods?
class BaseClass
attr_accessor :errors
def initialize
#errors = []
end
def valid?
#errors.empty?
end
class << self
def validates_presence_of(attribute)
begin
# HERE IS THE PROBLEM, self HERE IS THE CLASS NOT THE INSTANCE!
data = self.send(attribute)
if data.empty?
#errors << ["#{attribute} can't be blank"]
end
rescue
end
end
def validates_numericality_of(attribute)
begin
data = self.send(attribute)
if data.empty? || !data.integer?
#valid = false
#errors << ["#{attribute} must be number"]
end
rescue
end
end
end
end
Looking at ActiveModel, you can see that it doesn't do the actual validation when validate_presence_of is called. Reference: presence.rb.
It actually creates an instance of a Validator to a list of validators (which is a class variable _validators) via validates_with; this list of validators is then called during the record's instantiation via callbacks. Reference: with.rb and validations.rb.
I made a simplified version of the above, but it is similar to what ActiveModel does I believe. (Skipping callbacks and all that)
class PresenceValidator
attr_reader :attributes
def initialize(*attributes)
#attributes = attributes
end
def validate(record)
begin
#attributes.each do |attribute|
data = record.send(attribute)
if data.nil? || data.empty?
record.errors << ["#{attribute} can't be blank"]
end
end
rescue
end
end
end
class BaseClass
attr_accessor :errors
def initialize
#errors = []
end
end
EDIT: Like what SimpleLime pointed out, the list of validators will be shared across and if they are in the base class, it would cause all the items to share the attributes (which would obviously fail if the set of attributes are any different).
They can be extracted out into a separate module Validations and included but I've left them in in this answer.
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
##_validators = []
def initialize(attributes = {})
super()
#price = attributes[:price]
#name = attributes[:name]
end
def self.validates_presence_of(attribute)
##_validators << PresenceValidator.new(attribute)
end
validates_presence_of :name
def valid?
##_validators.each do |v|
v.validate(self)
end
#errors.empty?
end
end
p Item.new(name: 'asdf', price: 2).valid?
p Item.new(price: 2).valid?
References:
presence.rb
with.rb
validators.rb
class variable _validators
First, let's try to have validation baked into the model. We'll extract it once it's working.
Our starting point is Item without any kind of validation:
class Item
attr_accessor :name, :price
def initialize(name: nil, price: nil)
#name = name
#price = price
end
end
We'll add a single method Item#validate that'll return an array of strings representing errors messages. If a model is valid the array will be empty.
class Item
attr_accessor :name, :price
def initialize(name: nil, price: nil)
#name = name
#price = price
end
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
private
def validators
[]
end
end
Validating a model means iterating over all associated validators, running them on the model and collecting results. Notice we provided a dummy implementation of Item#validators that returns an empty array.
A validator is an object that responds to #run and returns an array of errors (if any). Let's define NumberValidator that verifies whether a given attribute is an instance of Numeric. Each instance of this class is responsible for validating a single argument. We need to pass the attribute name to the validator's constructor to make it aware which attribute to validate:
class NumberValidator
def initialize(attribute)
#attribute = attribute
end
def run(model)
unless model.public_send(#attribute).is_a?(Numeric)
["#{#attribute} should be an instance of Numeric"]
end
end
end
If we return this validator from Item#validators and set price to "foo" it'll work as expected.
Let's extract validation-related methods to a module.
module Validation
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
private
def validators
[NumberValidator.new(:price)]
end
end
class Item
include Validation
# ...
end
Validators should be defined on a per-model basis. In order to keep track of them, we'll define a class instance variable #validators on the model class. It'll simply by an array of validators specified for the given model. We need a bit of meta-programming to make this happen.
When we include any model into a class then included is called on the model and receives the class the model is included in as an argument. We can use this method to customize the class at inclusion time. We'll use #class_eval to do so:
module Validation
def self.included(klass)
klass.class_eval do
# Define a class instance variable on the model class.
#validators = [NumberValidator.new(:price)]
def self.validators
#validators
end
end
end
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
def validators
# The validators are defined on the class so we need to delegate.
self.class.validators
end
end
We need a way to add validators to the model. Let's make Validation define add_validator on the model class:
module Validation
def self.included(klass)
klass.class_eval do
#validators = []
# ...
def self.add_validator(validator)
#validators << validator
end
end
end
# ...
end
Now, we can do the following:
class Item
include Validation
attr_accessor :name, :price
add_validator NumberValidator.new(:price)
def initialize(name: nil, price: nil)
#name = name
#price = price
end
end
This should be a good starting point. There're lots of further enhancements you can make:
More validators.
Configurable validators.
Conditional validators.
A DSL for validators (e.g. validate_presence_of).
Automatic validator discovery (e.g. if you define FooValidator you'll automatically be able to call validate_foo).
If your goal is to mimic ActiveRecord, the other answers have you covered. But if you really want to focus on a simple PORO, then you might reconsider the class methods:
class Item < BaseClass
attr_accessor :price, :name
def initialize(attributes = {})
#price = attributes[:price]
#name = attributes[:name]
end
# validators are defined in BaseClass and are expected to return
# an error message if the attribute is invalid
def valid?
errors = [
validates_presence_of(name),
validates_numericality_of(price)
]
errors.compact.none?
end
end
If you need access to the errors afterwards, you'll need to store them:
class Item < BaseClass
attr_reader :errors
# ...
def valid?
#errors = {
name: [validates_presence_of(name)].compact,
price: [validates_numericality_of(price)].compact
}
#errors.values.flatten.compact.any?
end
end
I don't understand the point to implement PORO validations in Ruby. I'd do that in Rails rather than in Ruby.
So let's assume you have a Rails project. In order to mimic the Active Record validations for your PORO, you need to have also 3 things:
Some kind of a save instance method within your PORO (to call the validation from).
A Rails controller handling CRUD on your PORO.
A Rails view with a scaffold flash messages area.
Provided all 3 these conditions I implemented the PORO validation (just for name for simplicity) this way:
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
include ActiveModel::Validations
class MyValidator
def initialize(attrs, record)
#attrs = attrs
#record = record
end
def validate!
if #attrs['name'].blank?
#record.errors[:name] << 'can\'t be blank.'
end
raise ActiveRecord::RecordInvalid.new(#record) unless #record.errors[:name].blank?
end
end
def initialize(attributes = {})
#price = attributes[:price]
#name = attributes[:name]
end
# your PORO save method
def update_attributes(attrs)
MyValidator.new(attrs, self).validate!
#...actual update code here
save
end
end
In your controller you have to manually process the exception (as your PORO is outside ActiveRecord):
class PorosController < ApplicationController
rescue_from ActiveRecord::RecordInvalid do |exception|
redirect_to :back, alert: exception.message
end
...
end
And in a view - just a common scaffold-generated code. Something like this (or similar):
<%= form_with(model: poro, local: true) do |form| %>
<% if poro.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(poro.errors.count, "error") %> prohibited this poro from being saved:</h2>
<ul>
<% poro.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name, id: :poro_name %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
That's it. Just keep it all simple.
My Model
class User < ActiveRecord::Base
end
My Controller
class UsersController < ApplicationController
...
end
When I post addition field to post action such as { "xx": 7 ... }
{"user_email"=>"scott#student", "user_pw"=>"123456", "xx"=>7, "controller"=>"users", "action"=>"create", "user"=>{"user_pw"=>"123456", "user_email"=>"scott#student"}}
I can not found the "xx=>7" in "user" attribute. How can I move it to param["user"]["xx"].
Here is my code
due to "xx" is not UserModel attributes so, I guess that UserController using fields of UserModel to filter all parameters automatically, if the controllerName is mapped with modelName with same naming rule. So finnally I missing "xx" in user node inside.
class User < ActiveRecord::Base
# include fields: uid, user_name, user_mobile.
end
class UsersController < ApplicationController
def user_params
params.fetch(:user).extract!(:uid).permit!
end
def create
user = User.new(user_params)
user.save!
render :nothing => true, :status => 202
end
end
I can set a trick wrap_parameter setting in controller
wrap_parameters :user, include: User.column_names << 'xx'
or
wrap_parameters true
# but user node will be "true" => {"xxx": "test", "user_name": ... }, acception all field even if it's not UserModel attributes.
Finally, I want that the wrap_parameters 'user' without setup include all columns which client send to server. I don't want to setup like include: [*column_names].
It looks like you are currently relying on parameter wrapping, in particular you're letting it work in its most automatic mode. In this scenario if there is a param named foo submitted to UsersController and if the model User has a field of that name then rails copies it into params[:user].
You can change this behaviour in two ways: you could redefine attribute_names on your model to return this extra column. I'm not sure what else uses that method though - may have unintended consequences elsewhere.
The less disruptive change would be to put
wrap_parameters User, include: [:user_pw, :user_email, :xx]
At the top of the controller, to explicitly tell action controller which params should be treated in this way.
In your form partial/view you have to put the hidden attribute or however you set the xx value inside the form.
<%= form_for #user, do |f| %>
......
<%= f.hidden_field :xx, value: "7" %>
<% end %>
This way it will be under "user".
I have a form to edit a page, while it tells it's not a variable from what I have known from related questions. In view, an error is raised from this line:
<%= form_for #wiki, :url => giki_path(#wiki.name), :html => { :method => :put } do |f| %>
Where the #wiki does seem to be an instance, which can be confirmed by:
$ rails console
> #wiki
#<Gollum::Page:70026260995800 Home (markdown) #wiki="path/to/git/wiki/.git">
> #wiki.name
"/wiki/Home"
So I don't understand what is causing the problem:
undefined method `model_name' for #<Gollum::Page:0x007f6084d2bdb0>
Edit:
In controller:
# giki_controller.rb
def edit
#wiki = Wiki.find(params[:id])
end
# the same method, worked fine
def show
#wiki = Wiki.find(params[:id])
end
In model:
# wiki.rb
class Wiki
include ActiveModel::AttributeMethods
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :raw_data, :formatted_data, :title, :path, :change_desc, :versions
# Gollum Init
WIKI = Gollum::Wiki.new(Settings.wiki_repo, :base_path => "/wiki")
# initialize
def initialize(attributes = {})
attributes.each do |key, value|
send("#{key}=", value)
end
end
# no database
def persisted?
false
end
def self.find(name)
WIKI.page(name) # find a page by name
end
First lines from logger:
NoMethodError - undefined method `model_name' for #<Gollum::Page:0x007f607dfec4e8>:
actionpack (4.2.6) lib/action_controller/model_naming.rb:9:in `model_name_from_record_or_class'
actionview (4.2.6) lib/action_view/record_identifier.rb:47:in `dom_class'
Full traceback: I created a gist.
Your backtrace says that model_name is undefined in <Gollum::Page:0x007f607dfec4e8> which is an instance of Gollum::Page.
Reason
form_for method internally calls model_name method. This is actually a valid method name in ActiveRecord's instance.
Try
User.first.model_name
This model_name is not present in #wiki since this is not an instance of Wiki its rather the instance of Gollum::Page.
How can I say that?
Well, I saw you have overridden the self.find method in Wiki
def self.find(name)
WIKI.page(name) # find a page by name
end
so in your edit action, you have used find method to get the persisted instance, which will hand you over an instance Gollum::Page and this is not expected by form_for helper method.
Solution (Edited)
Well, if you were using ActiveRecord and wanted to continue the overridden self.find method then you can use where or find_by_x method instead in edit action. Like
def edit
#wiki = Wiki.find_by_id(params[:id])
end
But looks like you are not using ActiveRecord or your model is not derived from it, so you have to use the form_for method in different fashion.
If you don't need to attach a form to a model instance, then check out ActionView::Helpers::FormTagHelper#form_tag.
form_tag(giki_path(#wiki.name), method: :put)
I am using Mongoid(3.0.23) and I want to add nicer URL's, I have followed this rails cast but for some reason my site throws an undefined error for the find_by_slug method. I have read about some gems I could use but it seems pointless for such a simple task.
Model
validates :slug, :uniqueness => true
before_validation :generate_url
def generate_url
self.slug ||= self.title.parameterize if slug.blank?
end
def to_param
slug
end
field :slug
View
<% #events.each do |e| %>
<%= link_to e.title, event_path(e) %>
<% end %>
Controller
def show
#event = Event.find_by_slug!(params[:id])
end
Maybe try:
Event.find_by(slug: params[:id])
Also, not sure if it's necessary but you could specify the type:
field :slug, type: String
Mongoid defines the attribute finder, but not the bang version.
Event.find_by_slug(params[:id])
# => valid
Event.find_by_slug!(params[:id])
# => not defined
In any case, given the way ActiveModel is taking and according to best practices, it's better for you define all the public API of your model.
class Event
def self.find_by_slug!(slug)
where(slug: slug).first || raise(Mongoid::Errors::DocumentNotFound, self, slug: slug)
end
end
You can also re-use find_by_slug, but as I said, because ActiveRecord is deprecating find_by_attribute, I prefer to write the code directly.
... used in orderscontroller#new to create an array, which is used in a select method of a form field in views, which is supposed to add a new order
Hullo there,
So my error is this
Routing Error
undefined local variable or method `array_of_payment_types' for #
Now, I've got this select field for a form to submit a new order, but my browser won't load the page with the form:
<div class="field">
<%= f.label :pay_type %><br />
<%= f.select :payment_type_id, Order::PAYMENTS_TYPES,
:prompt => 'Select a payment method' %>
</div>
This is using an array that I am trying to create twice:
class Order < ActiveRecord::Base
...
belongs_to :payment_type
**PAYMENT_TYPES = array_of_payment_types**
validates :name, :address, :email
validates :pay_type, :inclusion => { :in => PAYMENT_TYPES }
...
**def array_of_payment_types
PaymentType.pluck(:pay_type_name)
end**
end
and here:
class OrdersController < ApplicationController
...
def new
#cart = current_cart
if #cart.line_items.empty?
redirect_to store_url, :notice => "Your cart is empty"
return
end
**#PAYMENT_TYPES = array_of_payment_types**
#hide_checkout_button = true
#order = Order.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #order }
end
end
...
end
while the method to create the array, which "plucks" the entries from the :pay_type_name column Payment_Type table, is declared both in order.rb and the ApplicationController, here:
class ApplicationController < ActionController::Base
protect_from_forgery
private
...
def array_of_payment_types
PaymentType.pluck(:pay_type_name)
end
end
Now I am trying to use the same process as other pieces of my application, just replicating stuff I have already done. For example, in OrdersController#new I've got
#cart = current_cart
current_cart is a method declared in the ApplicationsController and it works. So why doesn't array_of_payment_types also work?
Thanks for your help in advance :-)
Further information...
What I am trying to do with this is to create a new order, using a form, and one of the fields in the form enters a "pay_type" (or payment type in English). I want to present the user with options which is a list "plucked" from the entries in the PaymentType table, column :pay_type_name (I may be repeating myself, but no harm). But the new Order is created after the Order#new action, which is where I have created the array. Where/how should I create the array?
def array_of_payment_types in your Order class defines an instance method and you are trying to use it as a class method.
I'd just define array_of_payment_types as a class method instead and call Order.array_of_payment_types instead of the constant ARRAY_OF_PAYMENT_TYPES in your view.
You can always cache it in the class method, there's no need to use a constant for this.
def self.array_of_payment_types
#array_of_payment_types ||= PaymentType.pluck(:pay_type_name)
end
But, consider leaving the responsibility for the payment type array in the PaymentType class. The Order class shouldn't be the point of contact to retrieve data which is clearly under the control of another class.