Let's say I have two models; Post & Comment
class Post < ActiveRecord::Base
has_many :comments
accepts_nested_attributes_for :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
before_save :do_something
def do_something
# Please, let me do something!
end
end
I have a form for Post, with fields for comments. Everything works as expected, except for the filter. With the above configuration, before_save filter on Comment isn't triggered.
Could you explain why, and how I can fix this?
Rails doesn't instantiate and save the comments individually in this case. You would be better off adding a callback in your Post model to handle this for nested comments:
class Post < AR::Base
before_save :do_something_on_comments
def do_something_on_comments
comments.map &:do_something
end
end
According to Bryan Helmkamp, it's better to use the form object pattern than it is to use accepts_nested_attributes_for. Take a look at 7 Patterns to Refactor Fat ActiveRecord Models
Maybe you could do something like this?
class NewPost
include Virtus
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_reader :post
attr_reader :comment
# Forms are never themselves persisted
def persisted?
false
end
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
#post = Post.create!
#comment = #post.comment.create!
end
end
do_something would get called when you create the comment.
Related
I have the following models:
class Post < ApplicationRecord
has_one: :basic_metric
has_one: :complex_metric
end
class BasicMetric < ApplicationRecord
belongs_to :post
end
class ComplexMetric < ApplicationRecord
belongs_to :post
end
Once a post is created, both basic_metric and complex_metric are nil:
p = Post.first
p.basic_metric # => nil
p.complex_metric # => nil
And because of how my app is going to work, the BasicMetricsController and ComplexMetricsController only have the update method. So I would like to know if there is a way to create them as soon as a post is created.
One very common way of accomplishing this is using ActiveRecord callbacks
class Post
after_create :create_metrics
private
def create_metrics
# methods created by has_one, suggested in the comments
create_basic_metric(additional_attrs)
create_complex_metric(additional_attrs)
end
end
Another option you have is to overwrite the method created by has_one, i.e.:
class Post
has_one: :basic_metric
has_one: :complex_metric
def basic_metric
super || create_basic_metric(additional_attrs)
end
def complex_metric
super || create_complex_metric(additional_attrs)
end
end
This way they won't be created automatically with any new post, but created on demand when the method is called.
Can you try this one,
post = Post.first
post.build_basic_metric
post.build_complex_metric
This will help you to build/create the has_one association object if record not saved by default use post.save at the end.
If you need this in modal you can use like this,
class Post
after_create :build_association_object
private
def create_metrics
self.build_basic_metric
self.build_complex_metric
# save # (optional)
end
end
I am fairly new to Rails validations. I have an Activity model that has many attributes (listed in attributes array below). I need to validate that every activity has a name and a at least one of the other attributes. I was think of something like the following but it looks a little messy. Any advice?
class Activity < ActiveRecord::Base
belongs_to :user
validate :valid_activity
def valid_activity
attributes = [reps, distance, meal, post_meal, yoga, reminder, duration]
if name.present? && self.include? (activity)
end
end
end
You would create a new validator class like so
class ActivityValidator < ActiveModel::Validator
def valid_activity
attributes = [:reps, :distance, :meal, :post_meal, :yoga, :reminder, :duration]
unless name.present? && attributes.any?{ |a| self.activity == a }
errors[:user] << 'Need to add an activity'
end
end
end
Then in your user.rb file, include the validator module and use the validates_with method.
include ActiveModel::Validations
validates_with ActivityValidator
I have used AMS (0.8) with Rails 3.2.19 but one place where I really struggle with them is how to control whether serializers include their associations or not. I obviously use AMS to build JSON
Api's. Sometimes a serializer is the leaf or furthest out element and sometimes it's the top level and needs to include associations. My question is what is the best way to do this or is the solution I do below work (or is best solution).
I have seen some of the discussions but I find them very confusing (and version based). It's clear that for Serializer attributes or associations, there is an an include_XXX? method for each and you can return either a truthy or falsey statement here.
Here's my proposed code - it's a winemaker that has many wine_items. Is this how you would do this?
Model Classes:
class WineItem < ActiveRecord::Base
attr_accessible :name, :winemaker_id
belongs_to :winemaker
end
class Winemaker < ActiveRecord::Base
attr_accessible :name
has_many :wine_items
attr_accessor :show_items
end
Serializers:
class WinemakerSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :wine_items
def include_wine_items?
object.show_items
end
end
class WineItemSerializer < ActiveModel::Serializer
attributes :id, :name
end
and in my controller:
class ApiWinemakersController < ApplicationController
def index
#winemakers=Winemaker.all
#winemakers.each { |wm| wm.show_items=true }
render json: #winemakers, each_serializer: WinemakerSerializer, root: "data"
end
end
I ran into this issue myself and this is the cleanest solution so far (but I'm not a fan of it).
This method allows you to do things like:
/parents/1?include_children=true
or using a cleaner syntax like:
/parents/1?include=[children], etc...
# app/controllers/application_controller.rb
class ApplicationController
# Override scope for ActiveModel-Serializer (method defined below)
# See: https://github.com/rails-api/active_model_serializers/tree/0-8-stable#customizing-scope
serialization_scope(:serializer_scope)
private
# Whatever is in this method is accessible in the serializer classes.
# Pass in params for conditional includes.
def serializer_scope
OpenStruct.new(params: params, current_user: current_user)
end
end
# app/serializers/parent_serializer.rb
class ParentSerializer < ActiveModel::Serializer
has_many :children
def include_children?
params[:include_children] == true
# or if using other syntax:
# params[:includes].include?("children")
end
end
Kinda hackish to me, but it works. Hope you find it useful!
My application has a form_for tag with element :foo that is not saved in the model for the object used in form_for.
I need to confirm that the user submitted a value for this element, using Rails Validation Helpers. However, the 'presence' validator makes a call to object.foo to confirm that it has a value. Since foo is not saved as part of my object, how can I do this validation?
Thanks!
You should probably check for the presence of it in the params in your controller action:
def create
#model = MyModel.find(params[:id])
unless params[:foo].present?
#model.errors.add(:foo, "You need more foo")
end
# ...
end
If :foo is an attribute of your object that isn't saved in the database and you really want to use ActiveRecord Validations, you can create an attr_accessor for it, and validate presence like this.
class MyModel < ActiveRecord::Base
attr_accessor :foo
validates :foo, presence: true
end
But that could result in invalid records being saved, so you probably don't want to do it this way.
Try this..
class SearchController < ApplicationController
include ActiveModel::ForbiddenAttributesProtection
def create
# Doesn't have to be an ActiveRecord model
#results = Search.create(search_params)
respond_with #results
end
private
def search_params
# This will ensure that you have :start_time and :end_time, but will allow :foo and :bar
params.require(:val1, :foo).permit(:foo, :bar , whatever else)
end
end
class Search < ActiveRecord::Base
validates :presence_of_foo
private
def presence_of_foo
errors.add(:foo, "should be foo") if (foo.empty?)
end
end
See more here
I want to create tableless model which doesn't need datebase. At example:
class Post < ActiveRecord::Base
attr_accessible :body, :title, :language_id
belong_to :language
end
class Language
has_many :post
...
end
Will be 2 or 3 language. I don't want to load DB, is it possible to create languges in model by hand?
It might help to read this article: http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/.
In general, your models need not inherit from ActiveRecord, because you can include ActiveModel instead.
On the other hand, you can keep it simple like so:
class Langauge
attr_accessor :posts
def initialize
#posts = []
end
def add_post(post)
#posts << post
end
end
lang = Language.new
lang.add_post(Post.new)