assigning and reusing variables in Rails - design pattern - ruby-on-rails

I've been using the same pattern for returning json code (see example below). I'm getting a collection of photos and storing it in the variable. If tag param is present, I'm getting a more specific collection and reassigning it to the same variable. Then returning it as json. What would be a better design pattern to achieve the same thing?
photos = collection_of_photos
if params[:tag]
photos = photos.find_all {|photo| some condition}
end
render json: photos

If the photos are ActiveRecord objects you should use a scope to generate the appropriate query for the exact data you need. Otherwise, Rails will load all of the photos instead of the subset you need. Assuming your photos records have a single tag attribute, you can do something like the following:
class Photo < ApplicationRecord
scope :with_tag, (tag) -> { where(tag: tag) }
end
In your controller you'd only need the call that scope since ActiveRecord scopes return an #all scope if no parameters are provided:
render json: Photo.with_tag(params[:tag])
# Equivalent when params[:tag] is nil
render json: Photo.all
Now say you're not dealing with ActiveRecord objects. Say you have an array of hashes:
photos = [{ name: '1.jpg', tag: 'flower'}, ... ]
You can create a helper method to perform the filtering:
class PhotosController < ApplicationController
def index
photos = [{ name: '1.jpg', tag: 'flower'}, ... ]
render json: select_photos_by_tag(photos, params[:tag])
end
private
def select_photos_by_tag(photos, tag)
if tag
photos.select { |photo| photo[:tag] == tag }
else
photos
end
end
end

Related

How can i make this generic and make all the available attribute as filtering params?

I have multiple controllers. and I have a method to filter like this.
def filter(filtering_params)
results = where(nil)
filtering_params.each do |key, value|
results = results.public_send(key, value) if value.present?
end
results
end
and from controllers, I will call index or show APIs using filtering params for example like this.
def filtering_params
params.slice(:status, :created_at, :id, :transaction_datetime, :portfolio_external_reference_id, :file_id, :file_name)
end
which I will use while fetching from API like this.
def index
records = Module::Class.filter(filtering_params)
render json: {
data: records
}
end
I want to write a generic method that can be used in cases where I want all the available attributes in the particular model as the filtering_params. is there any better way than writing all the attributes?
I solved it and it's working for me.
def filter_params(params)
values = params.slice(*column_names.map(&:to_sym))
filter(values)
end

Doing search by count in json with params results

I'm trying to implement a counter filter in my json. When I access the url api/v1/contacts?Results=2 [sic], I would like to get only two results from my json.
For this, I created two methods in my controller: an index that takes the information from the database and turns render into json, and a method that returns the number of times the json is returned.
class Api::V1::ContactsController < ApplicationController
before_action :results, only: [:index]
def index
#contacts = Contact.all
render json: {results: #contacts[0..#number_results]}
end
def results
if params[:results]
results = params[:results]["results"]
#number_results = results.to_i
else
#number_results = 3
end
end
end
Regardless of the value entered for results =, the value of #number_results is set to 0, so whenever I type results = I, get only the first result of json. If I do not type results = in the url, it sets #number_results to 3, and shows four results on the screen.
Can anyone help?
First, in the url you propose, "Results" is capitalized. If that is how you intend to submit it, then you'll need to search for it that way on the back end. But since all your code uses lowercase "results", we'll go with that. You should modify your url in the same way: api/v1/contacts?results=2.
If that's what your url looks like then the number you pass in is accessible in params[:results]. If you pass no parameter, then params[:results] will return nil. Don't call #to_i on the param before you check for its existence, because nil.to_i is 0.
All that said, here's what you probably want:
class Api::V1::ContactsController < ApplicationController
def index
number_results = params[:results] || 3
#contacts = Contact.all.limit(number_results.to_i)
render json: {results: #contacts}
end
end
If the result of params[:results] is nil, then number_results is assigned 3.
Now we use #limit to return only the number of contacts that was requested. This allows us to do away with the #results method entirely. You can also get rid of the before_action callback.

Serialize an array of models using active_model_serializers

I am trying to send the serialized version of a model to a view as a param, using the gem active_model_serializers
#app/serializers/admin_serializer.rb
class AdminSerializer < ActiveModel::Serializer
attributes :id, :email, :access_locked?
end
#app/controllers/dashboard/admins_controller.rb
def index
#search = Admin.search(params[:q])
#admins = #search.result(:distinct => true).page(params[:page]).per(10)
#page_entries_info = view_context.page_entries_info #admins
# render json: #admins
respond_to do |format|
format.html
format.js
format.json {render json: #admins}
end
end
#app/views/dashboard/admins/index.html.erb
<%= debug (ActiveModel::Serializer::Adapter.adapter_class(:json_api).new(ActiveModel::Serializer.serializer_for(#admins.first).new(#admins.first),{}).to_json) %>
<%= debug (#admins.all.map{|admin| AdminSerializer.new(admin).to_json}) %>
Above debugs are yielding the below response:
--- '{"data":{"id":"1","type":"admins","attributes":{"email":"tech#bluesapling.com","access_locked?":false}}}' //returned by the first debug
---
- '{"object":{"id":36,"email":"aubrey_schmitt#feeneykoch.io","created_at":"2016-03-28T05:15:17.546Z","updated_at":"2016-03-28T05:15:17.546Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":20,"email":"alysa_johnston#thompson.io","created_at":"2016-03-28T05:15:16.304Z","updated_at":"2016-03-28T05:15:16.304Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":22,"email":"kristofer.langosh#kunzeluettgen.com","created_at":"2016-03-28T05:15:16.459Z","updated_at":"2016-03-28T05:15:16.459Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":37,"email":"beryl_keler#wiza.biz","created_at":"2016-03-28T05:15:17.624Z","updated_at":"2016-03-28T05:15:17.624Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":5,"email":"wilhelmine_buckridge#crona.io","created_at":"2016-03-28T05:15:15.139Z","updated_at":"2016-03-28T05:15:15.139Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":14,"email":"edward_wisoky#corkery.net","created_at":"2016-03-28T05:15:15.838Z","updated_at":"2016-03-28T05:15:15.838Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":27,"email":"leonor#jerde.biz","created_at":"2016-03-28T05:15:16.848Z","updated_at":"2016-03-28T05:15:16.848Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":2,"email":"carley#wyman.net","created_at":"2016-03-28T05:15:14.873Z","updated_at":"2016-03-28T05:15:14.873Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":10,"email":"ervin.gleichner#cremin.org","created_at":"2016-03-28T05:15:15.527Z","updated_at":"2016-03-28T05:15:15.527Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":15,"email":"lonzo.dickens#johnscole.name","created_at":"2016-03-28T05:15:15.916Z","updated_at":"2016-03-28T05:15:15.916Z"},"instance_options":{},"root":null,"scope":null}'
In the first debug I am serializing only one object, while in the second one I am trying to do it for an array of objects.
The first debug is correctly returning the serialized version of the object(in json_api format) while second debug is not.
Tried ArraySerializer as well, with no success: ActiveModel::Serializer::ArraySerializer.new(#admins, each_serializer: AdminSerializer).as_json
how do I achieve the desired serialization. Moreover, if achieved, can I used some other simplified version of this? As this debug statement is way too verbose.
Tried all the solutions mentioned here - How do you initialize an ActiveModel::Serializer class with an ActiveRecord::Relation array?
The basic problem which I am trying to solve is, in the index method of the Admin controller, the Admin object is passed as a PORO to the index.html file. But I want the serialized json version of this object so that I can pass it to my react components as a prop
index method is rendering proper json on firing http://dashboard.localhost.com:3000/admins.json
UPDATE#1 for the index method
def index
#search = Admin.search(params[:q])
#admins_array = #search.result(:distinct => true).to_a
if params[:page]
#admins = #search.result(:distinct => true).page(params[:page][:number]).per(10)
#admins_json_array = Kaminari.paginate_array(#admins_array).page(params[:page][:number]).per(10)
else
#admins = #search.result(:distinct => true).page(1).per(10)
#admins_json_array = Kaminari.paginate_array(#admins_array).page(1).per(10)
end
#admins_json = ActiveModel::SerializableResource.new(#admins_json_array.to_a)
...
...
...
end
I have a controller that I need to specify the serializer in, due to wanting different attributes from the default serializer.
In Controller:
def index
search = User.ransack(search_params)
render json: search.result, each_serializer: MembershipRenewalSerializer::MemberSerializer
end
So, just to get things working, what happens if you specify the each_serializer option?
Edits:
Outside Controller:
ActiveModel::SerializableResource.new(
User.first(2),
each_serializer: MembershipRenewalSerializer::MemberSerializer
).to_json
Note, that without specifying each_serializer, SerializableResource would use the UserSerializer.
Edit #2,
It looks like there is something weird happening with the #admins data.
Try converting to an array:
ActiveModel::SerializableResource.new(#admins.to_a).to_json
Edit #3
To paginate your array, try the following:
#search = Admin.search(params[:q])
#results = #search.result(:distinct => true).to_a
#admins = Kaminari.paginate_array(#results).page(params[:page]).per(10)
Follow the guide: Serializing before controller render
You could use ActiveModel::SerializableResource.new(#admins, adapter: :json_api).to_json
in index.html.erb
<%= debug (ActiveModel::SerializableResource.new(#posts, adapter: :json_api).to_json) %>
below is the output(using posts)
'{"data":[{"id":"1","type":"posts","attributes":{"title":"first post","body":null}},{"id":"2","type":"posts","attributes":{"title":"second post","body":null}}],"links":{}}
I create a concern with some API helper methods and there you can check if its a collection pass find the appropiate serializer and pass it to the collection serializer.
def api_response(data)
render json: wrap_answer(data)
end
def wrap_answer(data)
if data.respond_to?(:each)
ActiveModel::Serializer::CollectionSerializer.new(data, each_serializer: ActiveModel::Serializer.serializer_for(data.first))
else
data
end
end
should have made that into string and use json.stringify to make that as a string and make your life easy

Rails 3: Search method returns all models instead of specified

What I'm trying to do: I have a model "Recipe" in which I defined a method "search" that takes an array of strings from checkboxes (I call them tags), and a single string. The idea is to search the db for recipes that has anything in it's 'name' or 'instructions' that contains the string, AND also has any of the tags matching it's 'tags' property.
Problem: The search method return all the recipes in my db, and doesn't seem to work at all at finding by the specific parameters.
The action method in the controller:
def index
#recipes = Recipe.search(params[:search], params[:tag])
if !#recipes
#recipes = Recipe.all
end
respond_to do |format|
format.html
format.json { render json: #recipe }
end
end
The search method in my model:
def self.search(search, tags)
conditions = ""
search.present? do
# Condition 1: recipe.name OR instruction same as search?
conditions = "name LIKE ? OR instructions LIKE ?, '%#{search[0].strip}%', '%#{search[0].strip}%'"
# Condition 2: if tags included, any matching?
if !tags.empty?
tags.each do |tag|
conditions += "'AND tags LIKE ?', '%#{tag}%'"
end
end
end
# Hämtar och returnerar alla recipes där codition 1 och/eller 2 stämmer.
Recipe.find(:all, :conditions => [conditions]) unless conditions.length < 1
end
Any ideas why it return all records?
if you are using rails 3, then it is easy to chain find conditions
def self.search(string, tags)
klass = scoped
if string.present?
klass = klass.where('name LIKE ? OR instructions LIKE ?', "%#{string}%", "%#{string}%")
end
if tags.present?
tags.each do |tag|
klass = klass.where('tags LIKE ?', "%#{tag}%")
end
end
klass
end
When you do
search.present? do
...
end
The contents of that block are ignored - it's perfectly legal to pass a block to a function that doesn't expect one, however the block won't get called unless the functions decides to. As a result, none of your condition building code is executed. You probably meant
if search.present?
...
end
As jvnill points out, it is in general much nicer (and safer) to manipulate scopes than to build up SQL fragments by hand

Array of ActiveRecords to JSON

I am aware that ActiveRecord provides a to_json method which allows fields to be filtered out of the JSON output using :only and :except.
At present I am using the following to format an array from a find as JSON:
#customers = Customer.find(:all)
...
format.js { render :json => #customers}
How would I be able to select the fields to be output in the objects in the array? Is there a shortcut or do I need to do this by hand?
Cheers,
Adam
I think you answered your own question. With Rails 2.3.x you could use the following:
#customers = Customer.all #Shortcut for to Customer.find(:all)
respond_to do |format|
format.js { render :json => #customers.to_json(:only=>[:column_one, :column_two]}
end
You can overwrite the to_json method of the model class if you want to globally apply the change for the model.
For example, to exclude null values from the rendered JSON you could overwrite the original ActiveRecord method to_json
def to_json(options)
hash = Serializer.new(self, options).serializable_record
hash = { self.class.model_name => hash } if include_root_in_json
ActiveSupport::JSON.encode(hash)
end
with this in your model class:
def to_json(options)
hash = Serializer.new(self, options).serializable_record.reject {|key, value| value.nil? }
hash = { self.class.model_name => hash } if include_root_in_json
ActiveSupport::JSON.encode(hash)
end
If you peep into the ActionController::Base class, you'll see that it calls to_json on your collection immediately (no extra options used), so you've got to have it already prepared. So if in your action you don't use the attributes that are not rendered to json, you can replace your find with
#customers = Customer.find(:all, :select => ["id", ...])
to select only the ones that you need.

Resources