I got this model:
class Post
def rating_by(ip_address, user = nil)
if user
ratings.where("ratings.user_id = ?", user.id).first
else
ratings.where("ratings.ip_address = ?", ip_address).first
end
end
end
As you might notice, I allow ratings by both users and visitors.
I'd like to output posts as json with an additional user_rating attribute.
This is my current controller:
#posts = Post.trending(10)
respond_to do |format|
format.html
format.json { render json: #posts.to_json }
end
Of course this won't show it, but I'd like to know if there's a possibility of using to_json's :methods option and specify parameters as well, something like:
#posts.to_json(extra: {user_rating: "rating_by(#{request.remote_ip}, #{#current_user.id})"})
So that I end up with something like:
[{ id: 54, title: "Foo", user_rating: 8 }]
Other suggestions are very welcome!
I commented on your question, but I thought I would add some detail.
From Active Model Serializers' page :
class PersonSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, :full_name
def full_name
"#{object.first_name} #{object.last_name}"
end
end
So you could define a user_rating parameter in your serializer.
Moreover, Active Model Serializers allows the use of a meta
render json: #posts, meta: {total: 10}
So you could simply computer user_rating and send it with meta.
Related
I'm using ActiveModel Serializers to serialize my models and I'm constantly in need to create a new serializer in order to satisfy the needs of an controller without including unnecessary information into another.
class ContactGroupSerializer < ActiveModel::Serializer
attributes :id, :name, :contacts, :contacts_count,
:company_id, :user_id
def contacts_count
object.contacts.count
end
end
Is there a way to define a single serializer, such as the one above, and them dinamically select which attributes to be included on my controller response?
class ContactsGroupsController < ApplicationController
def index
...
render json: #contact_groups // here I would like to return only id and name, for example
end
end
I know I can achieve that by creating another serializer, but I wouldn't like to.
Well, you can just define a method in your application_controller.rb to which you can pass all your objects to be rendered with array of methods to be returned as response..like for example,
def response_for(object, methods = [:id])
if object.blank?
head :no_content
elsif object.errors.any?
render json: { errors: object.errors.messages }, status: 422
else
render json: build_hash_for(object, methods), status: 200
end
end
private #or in your `application_helper.rb`
def build_hash_for(object, methods)
methods.inject({}) do |hash, method|
hash.merge!(method => object.send(method))
end
end
In your particular case above, you can just
class ContactsGroupsController < ApplicationController
def index
...
response_for #contact_groups, [:id, :name]
end
end
I'm making a rails app where user can paste a soundcloud link in an input field. Then, this link is sent to the create action in my post_controller and used to get the JSON file for that song.
# app/controllers/post_controller.rb
def create
require 'open-uri'
raw_link = params[:post][:link]
raw_link.downcase.include? "soundcloud.com/"
tmp_media_json = JSON.load(open("http://soundcloud.com/oembed?format=json&url=#{raw_link}"))
if tmp_media_json['thumbnail_url']["placeholder"]
tmp_media_json['thumbnail_url'] = JSON.load(open("http://soundcloud.com/oembed?format=json&url=#{tmp_media_json['author_url']}"))['thumbnail_url']
end
media_thumbnail = tmp_media_json['thumbnail_url'].gsub('t500x500.jpg', 't80x80.jpg')
media_title = tmp_media_json['title']
media_iframe = tmp_media_json['html']
media_type = params[:post][:media_type]
#post = Post.new(link: media_iframe, title: media_title, thumbnail: media_thumbnail, media_type: media_type)
respond_to do |format|
if #post.save
format.js { render :file => "/pages/create_new_post.js.erb" }
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render action: 'show', status: :created, location: #post }
else
format.html { render action: 'new' }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
In the Post model, I'm trying to run validates :link, presence: true, but the problem is that it seems to be done after all the code in the create action. I want the validation to be done before all the code in the create action. (Since if there isn't a valid link, the code in the create action won't work).
How can I do this or is there a better practice?
class YourController < ApplicationController
before_filter :my_filter
before_filter :my_filter2, :except => [:index, :new, :create]
def my_filter
# your code gose here
end
def my_filter2
# your code gose here
end
........
end
You need to move the json fetching code out of the controller and into a model. Think Single Responsibility Principle(SRP): http://en.wikipedia.org/wiki/Single_responsibility_principle
Also, This question is slightly confusing because you are validating the "link" attribute which is the result of the JSON load from soundcloud, so this kind of makes your question impossible to achieve.
That being said, keep the controller lean
# controller...
def create
#post = Post.new_from_link(params[:post][:link], params[:post][:media_type])
#post.save
...render...
end
# post model...
class Post < ActiveRecord::Base
attr_accessor :raw_link, :raw_media_type
def new_from_link(raw_link, raw_media_type)
#raw_link = raw_link
#raw_media_type = raw_media_type
end
def assign_soundcloud_attributes
fetcher = SoundCloudFetcher.new(raw_link, raw_media_type).fetch
self.link = fetcher.link
self.media_type = fetcher.media_type
self.media_thumbnail = fetcher.media_thumbnail
self.media_iframe = fetcher.media_iframe
end
end
class SoundCloudFetcher
attr_accessor :link, :media_type, :media_thumbnail, :media_title, :media_iframe
def self.initialize(raw_link, raw_media_type)
#raw_link = raw_link
#raw_media_type = raw_media_type
end
def fetch
...go get and set the data...
return self
end
end
So the code above is not complete. Its missing the actual call to #assign_soundcloud_attributes. This design lets you move around where you want to do the call. You can place the call in #new_from_link, or you can place it in a before_validation or before_create callback, depending on your needs.
The question to clear this up is are you intending to validate the raw_link thats passed in, or do you want to validate the link that comes back from the soundcloud api call.
If you are validating the raw link, move the call to #assign_soundcloud_attributes to a before_create callback.
If you are validating the actual link attribute that is retrieved from the SoundCloud api call, then put it in the #new_from_link or #before_validation callback.
You can use a before_filter with rails_parms gem.
See https://github.com/nicolasblanco/rails_param
It works like this:
param! :q, String, required: true
param! :categories, Array
param! :sort, String, default: "title"
param! :order, String, in: %w(asc desc), transform: :downcase, default: "asc"
param! :price, String, format: /[<\=>]\s*\$\d+/
I am using Thinking Sphinx to run searches and I get the appropriate ActiveRecord Models fine. The problem is, I want to create an appropriate link path and text on each model, then send the info to the browser in the form of JSON, via AJAX. I am using the following to build those link attributes:
In the controller:
class FindController < ApplicationController
def tag_results
#results = ThinkingSphinx.search(params[:terms])
#results.each do |result|
result.build_ajax_response
end
respond_to do |format|
format.html
format.json { render :json => #results }
end
end
end
In the model:
class TaggedItem < ActiveRecord::Base
attr_accessible :name
attr_accessor :search_link, :search_text
def build_ajax_response
self.search_link = Rails.application.routes.url_helpers.tagged_item_path(self.id)
self.search_text = self.name
end
end
The resulting json object doesn't have either of the search_* attributes listed, much less have a value for them. I've tried using #search_link as well as just search_link in the build_ajax_response method.
Am I doing this wrong? Could there be something else interfering?
Rails' default to_json doesn't know about those extra non active record attributes you've added. The easiest possible thing is probably to specify them as extra methods to include:
format.json { render :json => #results.to_json(:methods => [:search_link, :search_text]) }
I want to display the related products to a certain need, just the picture and the name nothing more depending on the categorie
here is mu controller
class RelatedneedsController < ApplicationController
def index
#relatedneeds = RelatedNeed.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #relatedneeds }
end
end
def show
s1 = '#need.category.name'
s2 = '#relatedneed.category.name'
if s1.eql?(s2)
#relatedneed = relatedneed.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #need }
end
end
end
def get_name
#relatedneed.name
end
end
and this my model
class Relatedneed
include Mongoid::Document
include Mongoid::Paperclip
mapping do
indexes :name
end
field :name, type: String
belongs_to :category
belongs_to :user
end
and this is show.haml file
%h1
%b= #need.name
#container{:style => "width:1000px"}
#desc{:style => "height:400px;width:400px;float:left;"}
=image_tag #relatedneed.photo.url(:normal)
this is my index.haml file
%h1= #relatedneed.get_name
#container{:style => "width:1000px"}
#desc{:style => "background-color:#EEEEEE;height:400px;width:400px;float:left;"}
= link_to "Check Need", new_need_path
I don't know if their is something missing and i get this error
NoMethodError in RelatedneedsController#index
undefined method `key?' for nil:NilClass
Your index.haml has #relatedneed.get_name but you have not set #relatedneed in your controller, only #relatedneeds. Is it as simple as that?
Also, your show method in the controller makes no sense to me. You have put your instance variables inside strings! You need to set your instance variables with a database query via the model first. Your get_name method looks like it belongs in a model as well rather than a controller.
Here is the code generated by rails:
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
flash[:notice] = 'User was successfully updated.'
format.html { redirect_to(#user) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
But I don't want user to update the whole user, assume that my user have fname, lname and gender, instead of remove the gender from the view, I want to restrict that the update method ONLY accept fname and lname only, if he/she want to update the gender, I won't allow him/her to do so. How can I restrict the user to do so? thank you.
or add a custom #user.update_only() method, which makes it also easier to reuse in different contexts...
class User
def update_only(attrs = {}, *limit_to)
update_attributes(attrs.delete_if { |k,v| !limit_to.include?(k.to_sym) })
end
end
Then just do something along the lines of
#user.update_only(params[:user], :fname, :lname)
There are two methods in ActiveRecord that come in handy in cases like these, attr_protected and attr_accessible.
You use them like this:
class MyModel < ActiveRecord::Base
attr_accessible :fname, :lname #Allow mass-assignment
attr_protected :secret #Do not allow mass-assignment
end
model = MyModel.new(:fname => "Firstname", :lname => "Lastname", :secret => "haha")
puts model.fname # "Firstname"
puts model.lname # "Lastname"
puts model.secret = nil # Can not be set through mass-assignment
model.secret = "mysecret" # May only be assigned like this
puts model.secret # "mysecret"
However, if you only need this functionality at one place, then Salil's solution will work just as well.
One thing to note is that you should use attr_acessible to whitelist attributes that are OK to mass-assign, and make every other attribute protected. By doing so, you hinder mean people for updating data they are not supposed to touch.
See the docs for more info.
Use Hash parameters of the update_attributes
#user = User.find(params[:id])
#user.update_attributes(:fname=>params[:user][:fname], :lname=>params[:user][:lname])
You can delete unwanted attributes from the param[:user] Hash:
# ...
attributes = params[:user]
gender = attributes.delete :gender
raise SomeError unless gender.blank?
if #user.update_attributes(attributes)
# ...
end
# ...
This code removes :gender from the Hash and checks if it is filled in. If so, an exception is raised. Of course you could give a nice warning or silently ignore the fact that the gender was filled in.