Ruby on Rails 4 render :json with conditions - ruby-on-rails

I have this render expression in a Ruby on Rails 4 controller:
render :json => #account_preferences, :include => {:payment_methods => {:include => {:payment_type => {:only => [:id, :name]}}}}
Can I filter the payment_methods somehow in this expression to get only the payment_methods with state = 'Confirmed'?

I don't believe you can do this conditionally in the render :json.
But something like this would work:
class AccountPreference < ActiveRecord::Base
has_many :confirmed_payment_methods,-> { where state: 'Confirmed' }, class_name: 'PaymentMethod'
end
and call that association in your to_json
render :json => #account_preferences, :include => {:confirmed_payment_methods => {:include => {:payment_type => {:only => [:id, :name]}}}}

If you do a lot of includes and conditionals I'd recommend using jbuilder to do this kind of stuff.
Did you take a look at that?
https://github.com/rails/jbuilder
It's included in your Gemfile by default in a rails 4 project.

Related

Automatically update index with elasticsearch-rails

I'm implementing the completion suggester using the elasticsearch-rails gem. Everything works except update or delete.
For example when I update the title of an article and try to research again, the same title still exist.
I have included Elasticsearch::Model::Callbacks
Model:
require 'elasticsearch/model'
class Article < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
def self.suggest(query)
Article.__elasticsearch__.client.suggest(:index => Article.index_name, :body => {
:suggestions => {
:text => query,
:completion => {
:field => 'suggest'
}
}
})
end
settings :index => { :number_of_shards => 1 } do
mappings :dynamic => 'false' do
indexes :title, :type => 'string', :analyzer => 'english'
indexes :suggest, :type => 'completion', :index_analyzer => 'simple', :search_analyzer => 'simple', :payloads => true
end
end
def as_indexed_json(options={})
{
:name => self.title,
:suggest => {
:input => [self.title, self.content],
:output => self.title,
:payload => {
:id => self.id,
:content => self.content
}
}
}
end
end
Controller:
class ArticlesController < ApplicationController
def update
#article = Article.find(params[:id])
if #article.update_attributes(article_params)
render :json => #article
else
render :json => #article.errors
end
end
# ...
end
we had that same problem.
The only way to change the autocompletion data is to call the optimize API.
Optimize will cause a segment merge.
Completion suggesters are stored in their own datastructure calles FST. They are not part of the regular index, so just refreshing would not work.
The datastructure used to store completion suggestions is only created at index time, when a new segment is written. All the old data will be available until a full cleanup happens, which is only guaranteed when segments are merged.
So call optimize:
http://localhost:9200/indexName/_optimize?max_num_segments=number_of_segments
http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/merge-process.html#optimize-api
The smaller the number of segments the more performant completion will be.
And then check again. For me it worked!
Optimizing is slow and I/O heavy, so you cannot run it all the time, but maybe once a day or so...
Good luck!
https://groups.google.com/forum/?fromgroups#!searchin/elasticsearch/completion$20suggester$20delete/elasticsearch/8Rfg4kGV0ps/YG0V9jM7JhcJ
http://www.elasticsearch.org/blog/you-complete-me/

How add an attribute scoped in JSON in Rails 3

My aim is to add an attribute scoped in JSON, this some code :
User <ActiveRecord::Base
has_many :reviews
Review<ActiveRecord::Base
scope :best, lambda { |n| where('reviews.rating > ?',n}
And in my UsersController I want to do something like that :
format.json{
render :json => #user.to_json :include=> :reviews.best
I've tried as well something like :
format.json{
render :json => #user.to_json :include =>{ :reviews => {:method => best} }
But it doesn't work...
Any ideas ?
Thank you very much !
I would suggest you to use a gem for this, there is Active Record Serializers which should fit your needs or also Rabl, there is a RailsCast for this one.
Ok, I've figured out ! This is a solution :
In the User model we can add a virtual attribute like
def best_reviews
self.reviews.best
end
Then when we create json we can add :
format.json{
render :json => #user.to_json :methods => %w(best_reviews)
}
It does work ! But I don't know if it's the best solution

Mapping routes to layouts in Sinatra, help me clean this up

I'm a front end guy getting more and more into using sinatra. I'm building an app currently and am trying to find an elegant way to DRY up the routes in my myapp.rb file.
Currently I have these routes:
get '/' do
haml :content
end
get '/content' do
haml :content, :layout => :empty
end
get '/show_layout' do
haml :show_layout
end
get '/conversion' do
haml :conversion, :layout => :empty
end
get '/optout' do
haml :optout, :layout => false
end
get '/terms' do
haml :terms, :layout => :empty
end
With regards to the templates, I know I can do something like this to combine them:
get '/:name' do
haml params[:name].to_sym
end
But what about the layouts? There are only 2 layouts here, layout.haml and empty.haml (:layout, and :empty), 3 if you count :layout => false
Is this something that is best done using a hash? Something like:
layout_map = {
"" => "",
:content => "",
:show_layout => "",
:conversion => :empty,
:optout => false,
:terms => :empty
}
get '/:name' do
haml params[:name].to_sym, :layout => layout_map[:name]
end
Seems like its on the right track but I can't get it to work properly.
Thanks for all your help.
You can use your:
get '/:name' do
haml params[:name].to_sym
end
plus a before route that will set your layout:
before '/:name' do
layout_map = {
:content => "",
:show_layout => "",
:conversion => :empty,
:optout => false,
:terms => :empty
}
set :layout => layout_map[params[:name]]
end
This will set your layout according to params[:name] with every call. But be careful with .simming every route. If someone calls many 404s you create lots and lots of dead symbols which are not garbage collected and eventually will crash your app. Better do this:
get '/:name' do
halt 404 unless File.exist?("views/#{params[:name]}.haml")
time = File.stat("views/#{params[:name]}.haml").ctime
last_modified(time)
haml params[:name].intern
end
This will only create a symbol for params[:name] if there is a file with that name. So you're on the safe side because the symbol already exists.
Thanks for all your help everyone. Ended up going with this as my solution due to some other requirements of the app.
get "/:base_route/" do
haml :"#{params[:base_route]}/content", :layout => :"#{params[:base_route]}/layout"
end
get "/:base_route/:name" do
layout_map = {
:landing => :layout,
:content => :empty,
:show_layout => :layout,
:conversion => :empty,
:terms => :empty
}
haml :"#{params[:base_route]}/#{params[:name]}", :layout => :"#{params[:base_route]}/#{layout_map[params[:name].to_sym]}"
end

Import using FasterCSV / Rails 3.1 / Ruby 1.8.7

I'm attempting to import a tab separated file using FasterCSV. I've tried various things and get varying errors. In it's current state i'm getting a "undefined method `tempfile'" error.
I've added the fastercsv code to my create action as bulk import is the only way data will be added to this model.
Here's my code. Can anyone help please? Any assistance would be really appreciated!!
My Model:
class AppleSale < ActiveRecord::Base
end
My Controller:
require 'fastercsv'
require 'tempfile'
class AppleSalesController < ApplicationController
def new
#apple_sale = AppleSale.new
respond_to do |format|
format.html # new.html.erb
format.json { render :json => #apple_sale }
end
end
def create
file = params[:tsv_file]
FasterCSV.new(file.tempfile, {:headers => true, :quote_char=>'"', :col_sep =>"\t"}) do |row_data|
new_record = AppleSale.first_or_new(
'provider' => row_data['provider'],
'provider_country' => row_data['provider_country'],
'vendor_identifier' => row_data['vendor_identifier'],
'upc' => row_data['upc'],
'isrc' => row_data['isrc'],
'artist_show' => row_data['artist_show'],
'title' => row_data['title'],
'label_studio_network' => row_data['label_studio_network'],
'product_type_identifier' => row_data['product_type_identifier'],
'units' => row_data['units'],
'royalty_price' => row_data['royalty_price'],
'download_date' => row_data['download_date'],
'order_id' => row_data['order_id'],
'postal_code' => row_data['postal_code'],
'customer_identifier' => row_data['customer_identifier'],
'report_date' => row_data['report_date'],
'sale_return' => row_data['sale_return'],
'customer_currency' => row_data['customer_currency'],
'country_code' => row_data['country_code'],
'royalty_currency' => row_data['royalty_currency'],
'preorder' => row_data['preorder'],
'isan' => row_data['isan'],
'customer_price' => row_data['customer_price'],
'apple_identifier' => row_data['apple_identifier'],
'cma' => row_data['cma'],
'asset_content_flavor' => row_data['asset_content_flavor'],
'vendor_order_code' => row_data['vendor_order_code'],
'grid' => row_data['grid'],
'promo_code' => row_data['promo_code'],
'parent_identifier' => row_data['parent_identifier'],
'apple_identifier' => row_data['apple_identifier']
)
new_record.save
end
end
end
My Form View:
<%= form_for(#apple_sale, :multipart => true) do |f| -%>
<%= f.file_field :tsv_file %>
<%= f.submit "Upload >>", :class => "submit" %>
<% end %>
My Gemfile contains:
gem 'fastercsv'
Thanks in advance!!
UPDATE FOR OTHERS THAT MIGHT NEED THIS, I CAN'T ANSWER MY OWN QUESTION YET:
It was all in the controller, have changed to the following. This seems to work perfectly.
def create
uploaded_io = params[:apple_sale][:tsv_file]
File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'w') do |file|
file.write(uploaded_io.read)
end
FasterCSV.foreach(uploaded_io.original_filename, {:headers => true, :col_sep =>"\t"}) do |row_data|
new_record = AppleSale.new(
'provider' => row_data[0],
'provider_country' => row_data[1],
'vendor_identifier' => row_data[2],
'upc' => row_data[3],
'isrc' => row_data[4],
'artist_show' => row_data[5],
'title' => row_data[6],
'label_studio_network' => row_data[7],
'product_type_identifier' => row_data[8],
'units' => row_data[9],
'royalty_price' => row_data[10],
'download_date' => row_data[11],
'order_id' => row_data[12],
'postal_code' => row_data[13],
'customer_identifier' => row_data[14],
'report_date' => row_data[15],
'sale_return' => row_data[16],
'customer_currency' => row_data[17],
'country_code' => row_data[18],
'royalty_currency' => row_data[19],
'preorder' => row_data[20],
'isan' => row_data[21],
'customer_price' => row_data[22],
'apple_identifier' => row_data[23],
'cma' => row_data[24],
'asset_content_flavor' => row_data[25],
'vendor_order_code' => row_data[26],
'grid' => row_data[27],
'promo_code' => row_data[28],
'parent_identifier' => row_data[29]
)
new_record.save
end
respond_to do |format|
format.html { redirect_to apple_sales_path, :notice => "Successfully imported sales." }
end
end
1: I don't think FasterCSV accepts a block with new.
2: According to the Rails 3.1 docs - when you implement a file upload form:
http://guides.rubyonrails.org/form_helpers.html#uploading-files
...the resulting param is an IO object, which is not necessarily a plain file.
Looking at the FasterCSV source it looks like parse accepts an IO object + a block,
so I think something like this should do it:
FasterCSV.parse(file, ...) do |row_data|
...
end

problem using 'as_json' in my model and 'render :json' => in my controller (rails)

I am trying to create a unique json data structure, and I have run into a problem that I can't seem to figure out.
In my controller, I am doing:
favorite_ids = Favorites.all.map(&:photo_id)
data = { :albums => PhotoAlbum.all.to_json,
:photos => Photo.all.to_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
render :json => data
and in my model:
def as_json(options = {})
{ :name => self.name,
:favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : options[:favorite] }
end
The problem is, rails encodes the values of 'photos' & 'albums' (in my data hash) as JSON twice, and this breaks everything... The only way I could get this to work is if I call 'as_json' instead of 'to_json':
data = { :albums => PhotoAlbum.all.as_json,
:photos => Photo.all.as_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
However, when I do this, my :favorite => lambda option no longer makes it into the model's as_json method.......... So, I either need a way to tell 'render :json' not to encode the values of the hash so I can use 'to_json' on the values myself, or I need a way to get the parameters passed into 'as_json' to actually show up there.......
I hope someone here can help... Thanks!
Ok I gave up... I solved this problem by adding my own array methods to handle performing the operations on collections.
class Array
def to_json_objects(*args)
self.map do |item|
item.respond_to?(:to_json_object) ? item.to_json_object(*args) : item
end
end
end
class Asset < ActiveRecord::Base
def to_json_object(options = {})
{:id => self.id,
:name => self.name,
:is_favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : !!options[:favorite] }
end
end
class AssetsController < ApplicationController
def index
#favorite_ids = current_user.favorites.map(&:asset_id)
render :json => {:videos => Videos.all.to_json_objects(:favorite => lambda {|v| #favorite_ids.include?(v.id)}),
:photos => Photo.all.to_json_objects(:favorite => lambda {|p| #favorite_ids.include?(p.id)}) }
end
end
I think running this line of code
render :json => {:key => "value"}
is equal to
render :text => {:key => "value"}.to_json
In other words, don't use both to_json and :json.

Resources