Rails Fast Json API includes scope - ruby-on-rails

In my Rails 6/Grape API app I've got a serializer where I want to include only active journeys (by active I mean journey.is_deleted: false). Current endpoint looks like:
helpers do
def query
current_user.journey_progresses.joins(:journey).where('is_deleted', false)
end
end
get :journeys do
::Journeys::EnrolledJourneysSerializer.new(
query,
include: [:journey],
class: { Journey: ::Journeys::JourneyListSerializer },
)
end
It includes all journeys no matter if they have is_deleted: true or is_deleted: false. I want to include only journey with is_deleted: false to not show deleted journeys in the serialized response.
EnrolledJourneysSerializer
module Journeys
class EnrolledJourneysSerializer
include FastJsonapi::ObjectSerializer
belongs_to :journey, serializer: JourneyListSerializer
set_type :percent_progress
attributes :percent_progress, :started_at
end
end
JourneyListSerializer
module Journeys
class JourneyListSerializer
include FastJsonapi::ObjectSerializer
attribute :content do |object|
object.content.dig('attributes')
end
end
end
Is there any way different than default_scope on a Journey model?

This line is wrong and needs to be changed to...
current_user
.journey_progresses
.joins(:journey)
.where(journeys: { is_deleted: false })

Related

Implementing elasticsearch index update in ruby on rails application

I am creating a rails application with integration to elasticsearch for fulltext search. I have the search working for a model, however when another part of the app does an attribute_update on it, I get below error:
Elasticsearch::Transport::Transport::Errors::BadRequest ([400] {"error":"no handler found for uri [/cards/_doc/123/_update] and method [POST]"}):
I tried to add a call to update_document in the model, however, I still get the same error as above.
class Card < ApplicationRecord
include Searchable
validates :attr1, uniqueness: true
after_update :my_es_update
def my_es_update
self.__elasticsearch__.update_document
end
def self.es_full_search
response = Card.__elasticsearch__.search(
query: {
match_all: {}
},
size: 150
).results
#cards = response.results
end
Update call:
def pin
#card = Card.find(params[:id])
#card.update_attribute("pinned", true)
redirect_back(fallback_location: root_path)
end
What would be the right way to update the index in ES upon update to persistence layer?
Edit:
I have the Elasticsearch::Model::Callbacks module included in my model, per the documentation it should take care of updates...I am not sure what part of the setup I am missing here.
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
mapping do
indexes :name, type: :text
indexes :type, type: :text
indexes :size, type: :text
end
end
end
Update2:
I am using ES vsn 8.5.3 and the 2 below gems in my Gemfile for communicating with it:
gem 'elasticsearch'
gem 'elasticsearch-model', '~> 7.2.1'

Serialize relations in remote model

In my rails app I have 2 "Remote models". Those models are not active_record models and are retrieved on an API using a gem provided by the API.
I send data in hash to the library, and the library give me the data in the form of hashes. My question is more on how to generate correct hashes
My question can be illustrated with the 2 following models ;
remote_user.rb
class RemoteUser
include ActiveModel::Model
include ActiveModel::Serialization
attr_accessor(
:Name
:Email
...)
end
def attributes{ 'Name'=> nil,'Email'=>nil .....}
attr_reader(:HeadquartersAdress)
def HeadquartersAddress=(data={})
if data.is_a? RemoteAdresse
#HeadquartersAddress=data
else
#HeadquartersAddress=RemoteAdresse.new(data)
end
end
remote_adresse.rb
class RemoteAdresse
include ActiveModel::Model
include ActiveModel::Serialization
attr_accessor(
:AddressLine1,
:AddressLine2,
:City,
:Region,
:PostalCode,
:Country
)
def attributes
{
'AddressLine1'=>nil,
'AddressLine2'=>nil,
'City'=>nil,
'Region'=>nil,
'PostalCode'=>nil,
'Country'=>nil
}
end
end
Test :
test = RemoteUser.new Name: 'Foo'
test.HeadquartersAddress=RemoteAddress.new City: 'singapour'
test.serializable_hash
>{"Name"=>"Foo","HeadquartersAddress"=>#<RemoteAdresse:0xa9c2ef8
#City="singapour"}
I would prefer to have : {"Name"=>"Foo","HeadquartersAddress"=>{
"City"="singapour"}}
The nested object (adresse) is not serialized. What can I do to make it serialize too?
If I'm not entirely mistaken, you need to include associations to the serializable_hash call like so:
test = RemoteUser.new Name: 'Foo'
test.HeadquartersAddress=RemoteAddress.new City: 'singapour'
test.serializable_hash(include: :HeadquarterAddress)
If that doesn't work, there's always the possibility to overwrite read_attribute_for_serialization and adapted it for the HeadquarterAddress attribute.

Rails query of parent model doesn't retrieve child models

I have parent and child models on rails 5 with mongoid. When I query the parent, with .includes command - I can see rails trying to query mongo db - but the result json does not return the child objects.
Parent Model:
class Activity
include Mongoid::Document
field :title, type: String
has_many :activity_pictures
end
Child Model:
class ActivityPicture
include Mongoid::Document
field :name, type: String
belongs_to :activity, :class_name => 'Activity'
end
The controller methods:
def index
#activities = Activity.includes(:activity_pictures).all
end
def show
Activity.includes(:activity_pictures)
end
off course, I have updated activity_params:
def activity_params
params.require(:activity).permit(:title, :activity_pictures)
end
How do i get the full json data from http://localhost:3000/activities.json and the single object links?
Whilst the associations are being loaded with the use of includes, you need to specifically call the loaded association in order for it to render. Try
def index
#activities = Activity.includes(:activity_pictures).all
render json: #activities, include :activity_pictures
end
The answer above by margo was the right lead. I am using jbuilder though, so the solution was to change the file
index.json.jbuilder
as follows:
json.array! #activities do |activity|
json.title activity.title
json.activity_pictures activity.activity_pictures do |activity_picture|
json.name activity_picture.name
end
end

Validating nested models with ActiveModel::Validations

My application uses plain Ruby classes with ActiveModel::Validations, without implementing ActiveRecord:
class Car
include ::ActiveModel::Validations
attr_accessor :engine
end
class Engine
include ::ActiveModel::Validations
attr_accessor :cylinders
validates_presence_of :cylinders
end
I would like Car to check the nested attributes that are of ActiveModel::Validations, in this case engine.
car = Car.new
car.engine = Engine.new
car.engine.valid? # => false
car.valid? # => true
# It should return 'false',
# because 'engine.cylinders' is 'nil'
What's the easiest way to get this behavior?
One option is creating your own validation method, something like
class Car
include ::ActiveModel::Validations
attr_accessor :engine
validate :engine_must_be_valid
def engine_must_be_valid
errors.add(:base, "Engine is not valid") unless engine.valid?
end
end
I use Gem active_type in almost all my projects for similar requirements. Project statement - Make any Ruby object quack like ActiveRecord. Project github page provides good documentation as well.
in your Gemfile, add:
gem 'active_type'
Then,
class Car < ActiveType::Object
nests_one :engine
validates :check_engine
def check_engine
return true if self.engine.valid?
false
end
end
class Engine < ActiveType::Object
attribute :cylinders, :string
validates :cylinders, presence: true
end
Now,
car = Car.new
car.engine = Engine.new
car.engine.valid? # => false
car.valid? # => false

ActiveRecord - replace model validation error with warning

I want to be able to replace a field error with a warning when saving/updating a model in rails. Basically I want to just write a wrapper around the validation methods that'll generate the error, save the model and perhaps be available in a warnings hash (which works just like the errors hash):
class Person < ActiveRecord::Base
# normal validation
validates_presence_of :name
# validation with warning
validates_numericality_of :age,
:only_integer => true,
:warning => true # <-- only warn
end
>>> p = Person.new(:name => 'john', :age => 2.2)
>>> p.save
=> true # <-- able to save to db
>>> p.warnings.map { |field, message| "#{field} - #{message}" }
["age - is not a number"] # <-- have access to warning content
Any idea how I could implement this? I was able to add :warning => false default value to ActiveRecord::Validations::ClassMethods::DEFAULT_VALIDATION_OPTIONS
By extending the module, but I'm looking for some insight on how to implement the rest. Thanks.
The validation_scopes gem uses some nice metaprogramming magic to give you all of the usual functionality of validations and ActiveRecord::Errors objects in contexts other than object.errors.
For example, you can say:
validation_scope :warnings do |s|
s.validates_presence_of :some_attr
end
The above validation will be triggered as usual on object.valid?, but won't block saves to the database on object.save if some_attr is not present. Any associated ActiveRecord::Errors objects will be found in object.warnings.
Validations specified in the usual manner without a scope will still behave as expected, blocking database saves and assigning error objects to object.errors.
The author has a brief description of the gem's development on his blog.
I don't know if it's ready for Rails 3, but this plugin does what you are looking for:
http://softvalidations.rubyforge.org/
Edited to add:
To update the basic functionality of this with ActiveModel I came up with the following:
#/config/initializer/soft_validate.rb:
module ActiveRecord
class Base
def warnings
#warnings ||= ActiveModel::Errors.new(self)
end
def complete?
warnings.clear
valid?
warnings.empty?
end
end
end
#/lib/soft_validate_validator.rb
class SoftValidateValidator < ActiveModel::EachValidator
def validate(record)
record.warnings.add_on_blank(attributes, options)
end
end
It adds a new Errors like object called warnings and a helper method complete?, and you can add it to a model like so:
class FollowupReport < ActiveRecord::Base
validates :suggestions, :soft_validate => true
end
I made my own gem to solve the problem for Rails 4.1+: https://github.com/s12chung/active_warnings
class BasicModel
include ActiveWarnings
attr_accessor :name
def initialize(name); #name = name; end
warnings do
validates :name, absence: true
end
end
model = BasicModel.new("some_name")
model.safe? # .invalid? equivalent, but for warnings
model.warnings # .errors equivalent

Resources