Pass locals from controller to active model serializer - ruby-on-rails-4

I am trying to pass locals(prod: #product.id) to AMS in my controller, as follows:
#options_json = ActiveModel::SerializableResource.new(#option_types, prod: #product.id)
#options_json = #options_json.to_json(serialization_context: ActiveModelSerializers::SerializationContext.new(request), serialization_options: {prod: #product.id})
I am not sure whether to pass it during initialization or during call to to_json. Moreover I am unable to read this passed param in my OptionTypeSerializer. I've tried using options[:prod], serialization_options[:prod], serialization_opts[:prod] and a few different solutions that I found on stackoverflow, but none worked.
My AMS is pointed to master and in my gemfile.lock AMS version is active_model_serializers (0.10.0.rc5)
Also tried using:
#options_json = ActiveModel::SerializableResource.new(#option_types, serialization_context: ActiveModelSerializers::SerializationContext.new(request), prod: #product.id).to_json
but getting the value of instance_options[:prod] as null, although it is not null

Whenever I need to pass options into a serializer I do something like this:
# Controller
class CatalogPagesController < ApplicationController
# GET /catalog_pages/event/:id
def event
#catalog_pages = #event.catalog_pages
render json: #catalog_pages, index: true, each_serializer: Adm::CatalogPageSerializer
end
end
# Serializer
class CatalogPageSerializer < Adm::FormSerializer
attributes :id, :body, :catalog_page_templates
def include_catalog_page_templates?
return true unless #options[:index].present?
end
end
In this example I want to conditionally not return certain elements of JSON when coming from the index route.

Related

How to implement DelayedJob custom job for Prawn PDF rendering?

Im trying to use DelayedJob to render Prawn PDFs. Following the custom job code in the docs, I've come up with this:
/lib/jobs/pdf_handling.rb
RenderPdf = Struct.new( :id, :view_context ) do
def perform
user = User(id)
pdf = UserFolder.new( id, view_context )
name = "user_folder_report.pdf"
send_data pdf.render, filename: name, type: "application/pdf"
end
end
PagesController.rb
def user_folder
respond_to do |format|
format.pdf do
Delayed::Job.enqueue RenderPdf.new(#user, view_context)
end
end
end
this results in the error:
uninitialized constant PagesController::RenderPdf
Adding required RenderPdf at the top of the PagesController doesn't help.
What am I missing? How can I implement this so PDF generation occurs via DelayedJob? Thanks.
updates
When /jobs is moved under /apps the error changes to:
can't dump anonymous module: #<Module:0x007fca7a3ae638>
/application.rb
config.autoload_paths += Dir["#{config.root}/lib/assets/"]
updates
I changed
class RenderFolder < Struct.new( :type, :rating_id, :dis, :view_context )
def perform
to
class RenderFolder < ActiveJob::Base
def perform(...)
Then, using ActiveJob, you can do
RenderFolder.perform_later(...)
This seems to be working...Im still implementing.
the lib folder is no longer loaded by default in rails. you can either add it to the autoload_path or (what i would do) just have it in some app/xxx folder. typically, i have app/support or something for arbitrary utility classes.

Rails 4: strong_params,nested_attributes_for and belongs_to association trouble

I really can't get my head around Rails 4 strong parameters, belongs_to association and form with fields_for.
Imagine I have model for quoting some price:
class Quote < ActiveRecord::Base
belongs_to :fee
accepts_nested_attributes_for :fee
Now, I have seeded some fees into the db, and have put some radiobuttons on my form_for #quote using fields_for. The values of the radiobuttons are simply ids of the records.
Here is the troubling part, the controller:
def create
#quote = Quote.new(quote_params)
...
end
def quote_params
params.require(:quote).permit(:amount_from, fee_attributes: [:id])
end
From my understanding, automagically Rails should fetch fee record with some id, but there is some mystic error instead.
params hash is: "quote"=>{"amount_from"=>"1200", "fee_attributes"=>{"id"=>"1"}}
Log tail:
Completed 404 Not Found in 264ms
ActiveRecord::RecordNotFound (Couldn't find Fee with ID=1 for Quote with ID=)
app/controllers/quotes_controller.rb:14:in `create'
I really don't understand what is going on here, have read Rails association guide, googled for hour for all info, but to no avail.
What I want to achieve here is to understand the correct "Rails way" to fetch some associations for new Quote object using some params I've put in the form.
Guess I got nested_attributes_for wrong, somehow thought it would call Fee.find automagically.
I've opted for ditching fields_for helpers from the form and rendering fields manually like
radio_button_tag 'fee[id]', fee.id
Then in controller I have 2 params methods now:
def quote_params
params.require(:quote).permit(:amount_from)
end
def fee_params
params.require(:fee).permit(:id)
end
And my action looks like
def create
#quote = Quote.new(quote_params)
#quote.fee = Fee.find(fee_params[:id])
...
Any additions on best practices when one has to handle lots of different objects with not so straight init logic are welcome.

Virtual attributes in Rails 4 from Rails 3

I am creating a Rails 4.10 app and I am using code from my Rails 3.2 app.
In this app I got a form where I add a virtual attribute called group_ids and then use after_save to add groups to a user.
In my 3.2 code:
attr_accessor :group_ids
after_save :add_groups
def add_groups
if group_ids.present?
self.assignments.user_groups.delete_all
group_ids.reject!(&:empty?)
group_ids.each do |group|
assignment = Assignment.new
assignment.assignable_id = group
assignment.assignable_type = "UserGroup"
assignment.backend_user_id = self.id
self.assignments << assignment
end
end
end
In my 4.10 code:
Controller:
params.require(:backend_user).permit(:firstname, :lastname, :group_ids)
How can I use the Add_groups method in 4.10?
Looking at your code, it seems that group_ids are not being whitelisted correctly in the controller code resulting in group_ids not being set which in turn would result in group_ids.present? condition returning false in your after_save callback. So, none of the statements gets executed in the callback.
To cut it short, after_save callback add_groups is getting executed but its not doing anything.
To resolve this, you should be whitelisting group_ids as an Array in your controller code:
params.require(:backend_user).permit(:firstname, :lastname, :group_ids => [])

Rails 4 - Column exists but does not update

I have recently added a field "tag" to my blog app built in Rails 4. Below you can see the field appearing in the Edit view:
But once I return to the Show view after editing, this does not appear:
When I check the database directly I can definitely see it exists:
sqlite> PRAGMA table_info(POSTS);
0|id|INTEGER|1||1
1|title|varchar(255)|0||0
2|body|text|0||0
3|created_at|datetime|0||0
4|updated_at|datetime|0||0
5|slug|varchar(255)|0||0
6|tag|varchar(255)|0||0
Can anyone suggest what is going on or how to troubleshoot this?
Rails 4 uses strong parameters by default. This means you have to explicitly whitelist params you wish to mass assign.
When adding a new attribute to a model, you have to remember to update the permitted params in you controller.
For example, in your case, you would need to make sure :tags are added like so:
class PostController < ActionController::Base
def update
post = Post.find(params[:id])
post.update(post_params)
redirect_to post
end
private
def post_params
params.require(:post).permit(:title, :body, :tag)
end
end

Does ActiveModel::Serializer require an explicit render call?

I know that when using view templates (html, rabl), I don't need an explicit render call in my controller action because by default, Rails renders the template with the name corresponding to the controller action name. I like this concept (not caring about rendering in my controller code) and therefore wonder whether this is possible as well when using ActiveModel::Serializers?
Example, this is code from a generated controller (Rails 4.1.0):
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
#other actions
# GET /products/1
# GET /products/1.json
def show
end
end
and this is the serializer:
class ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :description, :url, :quantity, :price
end
Hitting /products/1.json, I would expect two things to happen:
Fields not listed in the serializer to be ommited,
Whole JSON object to be incapsulated within a 'product' top level field.
However, this does not happen, whole serializer is ignored. But then if I modify the Show method to the following:
# GET /products/1
# GET /products/1.json
def show
#product = Product.find(params[:id])
respond_to do |format|
format.html
format.json { render json: #product }
end
end
And now it is all fine, but I have lost the benefit of the before_action filter (and it seems to me that I have some redundant code).
How should this really be done?
Without an explicit render or respond_with or respond_to Rails will look for a matching template. If that template does not exist Rails throws an error.
However, you can create your own resolver to bypass this. For instance, suppose you created app\models\serialize_resolver.rb and put this into it:
class SerializeResolver < ActionView::Resolver
protected
def find_templates(name, prefix, partial, details)
if details[:formats].to_a.include?(:json) && prefix !~ /layout/
instance = prefix.to_s.singularize
source = "<%= ##{instance}.active_model_serializer.new(##{instance}).to_json.html_safe %>"
identifier = "SerializeResolver - #{prefix} - #{name}"
handler = ActionView::Template.registered_template_handler(:erb)
details = {
format: Mime[:json],
updated_at: Date.today,
virtual_path: "/#{normalize_path(name, prefix)}"
}
[ActionView::Template.new(source, identifier, handler, details)]
else
[]
end
end
def normalize_path(name, prefix)
prefix.present? ? "#{prefix}/#{name}" : name
end
end
And then, in either your application controller (or in an individual controller) place:
append_view_path ::SerializeResolver.new
With that you should be able to do what you want. If it is a json request, it will create an erb template with the right content and return it.
Limitations:
This is a bit clunky because it relies on erb, which is not needed. If I have time I will create a simple template handler. Then we can invoke that without erb.
This does wipe out the default json response.
It relies on the controller name to find the instance variable (/posts is converted to #post.)
I've only tested this a little. The logic could probably be smarter.
Notes:
If a template is present, it will be used first. That allows you to override this behavior.
You can't simply create a new renderer and register it, because the default process doesn't hit it. If the template is not found, you get an error. If the file is found, it goes straight to invoking the template handler.
The 'redundant code' we see in the second one, is this line only:
#product = Product.find(params[:id])
And I believe this is the same logic as your before_action. You don't need this line, just remove it. Now the duplication is removed.
To the remaining part. An action needs to know what to render. By default if the action is empty or absent, the corresponding 'action_name'.html.erb (and other formats specified by respond_to) will be looked up and rendered.
This is why what the Rails 4 generator created works: it creates the show.html.erb and show.json.jbuilder which get rendered.
With ActiveModel::Serializer, you don't have a template. If you leave the action empty, it doesn't have a clue what to render. Thus you need to tell it to render the #product as json, by either:
render json: #product
or
respond_with #product