node(:status) { #status }
node(:message) { #message }
object #patient
attributes :medication, :guardian_id, :id, :name, :email, :username, :address, :age, :gender
node :errors do |e|
e.errors
end
I want something like this
if object is patient
do this
eleif object is doctor
do this
How to apply condition in rabl file?
In rabl, you would just use normal Ruby for if-statements.
if !#patient.nil?
# do this
elsif !#doctor.nil?
# do that
end
I didn't know how you intended to use your objects, just guessed. Could also be something like if object == 'patient' etc...
Related
I am working on a Ruby on Rails project with ruby-2.6.0 and Rails 6. i am working on api part, i have used jsonapi-serializers gem in my app. I want to add conditional attribute in serializer.
Controller:
class OrderDetailsController < Api::V1::ApiApplicationController
before_action :validate_token
def show
user = User.find_by_id(#user_id)
order_details = user.order_details.where(id: params[:id])&.first
render json: JSONAPI::Serializer.serialize(order_details, context: { order_detail: true })
end
end
Serializer:
class OrderDetailSerializer
include JSONAPI::Serializer
TYPE = 'Order Details'
attribute :order_number
attribute :price
attribute :quantity
attribute :order_status
attribute :ordered_date, if: Proc.new { |record|
context[:order_detail] == true
}
end
So here i am passing the 'order_detail' from controller inside context.I am getting the below error:-
TypeError (#<Proc:0x00007fe5d8501868#/app_path/app/serializers/order_detail_serializer.rb:46> is not a symbol nor a string):
I have followed the conditional attributes as mentioned on the jsonapi-serializer and have tried to make my 'ordered_date' attribute conditional but it's not working.
Please guide me how to fix it. Thanks in advance.
You access context but it seems not defined. From the documentation
The record and any params passed to the serializer are available inside the Proc as the first and second parameters, respectively.
class OrderDetailSerializer
include JSONAPI::Serializer
TYPE = 'Order Details'
attribute :order_number
attribute :price
attribute :quantity
attribute :order_status
attribute :ordered_date, if: Proc.new { |record, params|
params[:context][:order_detail]
}
end
You also don't need to check for == true as the parameter is already a boolean.
If that doesn't work, you can always add a puts or debugger in your Proc to have a look what is happening in the Proc.
class OrderDetailSerializer
include JSONAPI::Serializer
TYPE = 'Order Details'
attribute :order_number
attribute :price
attribute :quantity
attribute :order_status
attribute :ordered_date, if: Proc.new { |record, params|
puts record.inspect
puts params.inspect
true
}
end
Looking at the example for conditional attributes, you simply need to pass params as a second parameter, so:
in controller:
render json: JSONAPI::Serializer.serialize(order_details, { params: { order_detail: true }})
in serializer:
attribute :ordered_data, if: Proc.new { |record, params|
params[:order_detail]
}
I'm creating my own website using Ruby on Rails. One thing that I've failed to comprehend is why and when to use attr:accessors in place of a permanent column for a model. For instance, let's say that I created a 'posts' model which would have a title, description and some content associated with it. Now should I do rails g model Post title:string description:text content:text or should I declare them as attr:accessible :title, :description, :content.
I'm not very experienced in rails, so please bear with me if this sounds too silly to you.
You can use attr_accessor if you need virtual attributes in model.
For eg: In contact us form you need not to see form data, but you need to send that data using email. So you can create attr_accessor for adding virtual attributes and can also apply validations on that.
class Contact
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
ref
attr_accessible is to white list of attributes that can be mass assigned in model.
class Comment < ActiveRecord::Base
attr_accessible :user_id, :content
end
def create
#so here params[:comment], have all parameters. But if those params are not in attr_accessible list it will not save it.
# you can try that by remove attr_accessible from model
#comment = Comment.new(params[:comment])
if #comment.save
flash[:notice] = "Successfully created comment."
redirect_to #comment
else
render :action => 'new'
end
end
Comment Form:
<%= form_for #comment do |f| %>
<%= f.error_messages %>
<%= f.hidden_field :user_id, value: current_user.id %>
<p>
<%= f.label :content %><br />
<%= f.text_area :content %>
</p>
<p><%= f.submit %></p>
<% end %>
Happy Coding...
To add to Pardeep's epic answer, you'll want to look at this RailsCast (RE "virtual attributes"):
attr_accessor basically creates a setter & getter method in the model.
Probably doesn't make any sense; what you have to remember is that each Rails model is a class. Classes form the backbone of object-orientated programming.
Since Ruby is object orientated, each time you do anything with the language, it expects classes to be invoked & manipulated. The basis of OOP is to load classes into memory & play with them; good write-up here.
In classic OOP, your classes would be hard-coded with a series of attributes:
class Mario
def jump
pos_y + 5
end
def pos_y
# gets y val from the viewport
end
end
This will allow you to send instructions to the program, in turn modifying the class:
#mario.jump
... this should modify the viewport etc in the way you defined within the class.
--
Rails is very similar to the above, except most of the attributes are defined by ActiveRecord;
#app/models/mario.rb
class Mario < ActiveRecord::Base
# attributes from db include height, weight, color etc
end
Rails models allow you to call:
#mario = Mario.find x
#mario.height = "255"
... however, they don't allow you to create attributes which are stored in memory only.
For example...
#app/models/mario.rb
class Mario < ActiveRecord::Base
attr_accessor :grown
end
The above will give you an instance value of grown, which will allow you to populate this independently of the database.
So say you wanted to...
#mario = Mario.find x
#mario.grown = true if params[:grown]
#mario.height += "150" if #mario.grown
Regarding the difference between attr_accessor and attr_accessible, you'll want to look up Rails 3 and mass assignment.
I came into Rails ~ 4.0, so I didn't have to deal with attr_accessible so much; it was basically the way to permit parameters in Rails 3 models.
In Rails 4/5, you use strong params in the controller:
#app/controllers/mario_controller.rb
class MarioController < ApplicationController
def create
#mario = Mario.new mario_params
#mario.save
end
private
def mario_params
params.require(:mario).permit(:x, :y, :z)
end
end
Let's say that I have an input field with a value, and I want to validate it (on the server side) to make sure, for instance, that the field has at least 5 characters.
The problem is that it is not something that I want to save in the database, or build a model. I just want to check that the value validates.
In PHP with Laravel, validation is quite easy:
$validator = Validator::make($data, [
'email' => ['required', 'email'],
'message' => ['required']]);
if ($validator->fails()) { // Handle it... }
Is there anything similar in Rails, without need of ActiveRecord, or ActiveModel? Not every data sent from a form makes sense as a Model.
You can use ActiveModel::Validations like this
class MyClass
include ActiveModel::Validations
validates :email, presence: true
validates :message, presence: true
end
It will act as a normal model and you will be able to do my_object.valid? and my_object.errors.
Rails validations live in ActiveModel so doing it without ActiveModel seems kind of counter-productive. Now, if you can loosen that requirement a bit, it is definitely possible.
What I read you asking for, and as I read the PHP code doing, is a validator-object that can be configured on the fly.
We can for example build a validator class dynamically and use instance of that class to run our validations. I have opted for an API that looks similar to the PHP one here:
class DataValidator
def self.make(data, validations)
Class.new do
include ActiveModel::Validations
attr_reader(*validations.keys)
validations.each do |attribute, attribute_validations|
validates attribute, attribute_validations
end
def self.model_name
ActiveModel::Name.new(self, nil, "DataValidator::Validator")
end
def initialize(data)
data.each do |key, value|
self.instance_variable_set("##{key.to_sym}", value)
end
end
end.new(data)
end
end
Using DataValidator.make we can now build instances of classes with the specific validations that we need. For example in a controller:
validator = DataValidator.make(
params,
{
:email => {:presence => true},
:name => {:presence => true}
}
)
if validator.valid?
# Success
else
# Error
end
group controller:
def show
#cat = Category.find_by_id(params[:id])
if #cat.group
#group = #cat.group
#members = #cat.group.group_members.all
#mem = #group.group_members.build
else
#cat.build_group
#cat.save
#mem = #cat.group.group_members.build
end
end
def add_member
#cat = Category.find_by_id(params[:id])
#group = #cat.group
#group.group_members.build(member_params)
if #group.save
redirect_to group_path
end
view:
- if #members.length > 0
- #members.each do |member|
%ul
%li
= member.first_name
= simple_form_for #mem, url: member_add_path(#cat.id), html: {:id => 'step_two_form'} do |f|
= f.label "First name"
= f.input :first_name, label: false
= f.label "Last name"
= f.input :last_name, label: false
= f.label "Email"
= f.input :email, label: false
= f.label "Phone number"
= f.input :telephone, label: false
= f.button :button, "Add member"
When I submit this form I can see that a new object is created as there is a new <li> in the source however the object has blank values, regardless of the input.
params (in the group controller):
def member_params
params.require(:group_member).permit(group_members_attributes: [:first_name, :last_name, :email, :telephone, :relationship, :status])
end
In the terminal I can see that the values I input are being passed but for some reason are not being saved. Here is the terminal output:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"7odxnZzHoyjgF/oougDIVKNR/9RkZOlK3IOpCaUVvpQ=", "group_member"=>{"first_name"=>"name", "last_name"=>"name", "email"=>"name#name.com", "telephone"=>"1234567890"}, "button"=>"", "id"=>"22"}
All help is appreciated, thank you.
EDIT:
group_member.rb:
class GroupMember < ActiveRecord::Base
belongs_to :group
attr_accessor :first_name, :last_name, :email, :telephone, :relationship
end
Your member_params method doesn't need to specify group_members_attributes as a nested hash, you should just be able to permit the attributes directly (they'll be permitted on whatever you put in the require(...) part):
def member_params
params.require(:group_member).permit(:first_name, :last_name, :email, :telephone, :relationship, :status)
end
Extending #DylanMarkow's answer, if you are trying to save first_name, last_name, email, telephone, relationship fields in database then you need to remove the following line from GroupMember model:
attr_accessor :first_name, :last_name, :email, :telephone, :relationship
Due to the attr_accessor, the above mentioned fields are considered as virtual attributes and hence not saved in database.
UPDATE
Can you briefly explain what the purpose of attr_accessor is? I
thought it creates a getter and setter methods for the listed
attributes?
Yes, attr_accessor is a Ruby method and it creates getter and setter methods for an attribute. When you use attr_accessor within a Rails model, the listed attributes are treated as virtual attributes i.e., there values would be in memory/ accessible only till the instance of the model lives because it is not stored in the database fields (as it is marked as virtual attribute). In a Rails model you don't need to worry about getters and setters of attributes as ActiveRecord would take care of that.
combined with #DylanMarkows answer you are saving the group but not necessarily the member
def add_member
#cat = Category.find_by_id(params[:id])
#group = #cat.group
#group_member = #group.group_members.build(member_params)
if #group_member.save
redirect_to group_path
else
render ...
end
end
that might help also your method is missing an end and probably an else on save
I currently have a model Attend that will have a status column, and this status column will only have a few values for it. STATUS_OPTIONS = {:yes, :no, :maybe}
1) I am not sure how i can validate this before a user inserts an Attend? Basically an enum in java but how could i do this in rails?
Now that Rails 4.1 includes enums you can do the following:
class Attend < ActiveRecord::Base
enum size: [:yes, :no, :maybe]
validates :size, inclusion: { in: sizes.keys }
end
Which then provides you with a scope (ie: Attend.yes, Attend.no, Attend.maybe), a checker method to see if certain status is set (ie: #yes?, #no?, #maybe?), along with attribute setter methods (ie: #yes!, #no!, #maybe!).
Rails Docs on enums
Create a globally accessible array of the options you want, then validate the value of your status column:
class Attend < ActiveRecord::Base
STATUS_OPTIONS = %w(yes no maybe)
validates :status, :inclusion => {:in => STATUS_OPTIONS}
end
You could then access the possible statuses via Attend::STATUS_OPTIONS
This is how I implement in my Rails 4 project.
class Attend < ActiveRecord::Base
enum size: [:yes, :no, :maybe]
validates :size, inclusion: { in: Attend.sizes.keys }
end
Attend.sizes gives you the mapping.
Attend.sizes # {"yes" => 0, "no" => 1, "maybe" => 2}
See more in Rails doc
You could use a string column for the status and then the :inclusion option for validates to make sure you only get what you're expecting:
class Attend < ActiveRecord::Base
validates :size, :inclusion => { :in => %w{yes no maybe} }
#...
end
What we have started doing is defining our enum items within an array and then using that array for specifying the enum, validations, and using the values within the application.
STATUS_OPTIONS = [:yes, :no, :maybe]
enum status_option: STATUS_OPTIONS
validates :status_option, inclusion: { in: STATUS_OPTIONS.map(&:to_s) }
This way you can also use STATUS_OPTIONS later, like for creating a drop down lists. If you want to expose your values to the user you can always map like this:
STATUS_OPTIONS.map {|s| s.to_s.titleize }
For enums in ActiveModels you can use this gem Enumerize
After some looking, I could not find a one-liner in model to help it happen. By now, Rails provides Enums, but not a comprehensive way to validate invalid values.
So, I opted for a composite solution: To add a validation in the controller, before setting the strong_params, and then by checking against the model.
So, in the model, I will create an attribute and a custom validation:
attend.rb
enum :status => { your set of values }
attr_accessor :invalid_status
validate :valid_status
#...
private
def valid_status
if self.invalid_status == true
errors.add(:status, "is not valid")
end
end
Also, I will do a check against the parameters for invalid input and send the result (if necessary) to the model, so an error will be added to the object, thus making it invalid
attends_controller.rb
private
def attend_params
#modify strong_params to include the additional check
if params[:attend][:status].in?(Attend.statuses.keys << nil) # to also allow nil input
# Leave this as it was before the check
params.require(:attend).permit(....)
else
params[:attend][:invalid_status] = true
# remove the 'status' attribute to avoid the exception and
# inject the attribute to the params to force invalid instance
params.require(:attend).permit(...., :invalid_status)
end
end
To define dynamic behavior you can use in: :method_name notation:
class Attend < ActiveRecord::Base
enum status: [:yes, :no, :maybe]
validates :status, inclusion: {in: :allowed_statuses}
private
# restricts status to be changed from :no to :yes
def allowed_statuses
min_status = Attend.statuses[status_was]
Attend.statuses.select { |_, v| v >= min_status }.keys
end
end
You can use rescue_from ::ArgumentError.
rescue_from ::ArgumentError do |_exception|
render json: { message: _exception.message }, status: :bad_request
end
Want to place another solution.
#lib/lib_enums.rb
module LibEnums
extend ActiveSupport::Concern
included do
validate do
self.class::ENUMS.each do |e|
if instance_variable_get("#not_valid_#{e}")
errors.add(e.to_sym, "must be #{self.class.send("#{e}s").keys.join(' or ')}")
end
end
end
self::ENUMS.each do |e|
self.define_method("#{e}=") do |value|
if !self.class.send("#{e}s").keys.include?(value)
instance_variable_set("#not_valid_#{e}", true)
else
super value
end
end
end
end
end
#app/models/account.rb
require 'lib_enums'
class Account < ApplicationRecord
ENUMS = %w(state kind meta_mode meta_margin_mode)
include LibEnums
end