I have a user model and an email model. The user can create emails and receive them. A received email is realy just an email, but joined to the user on different field. My setup is below, but it doesn't work correctly. When I call received_messages on a user that created an email (and did not receive it) he also sees a message.
Is this possible at all this way?
class User < ActiveRecord::Base
has_many :mail_messages
has_many :received_messages, class_name: 'MailMessage', foreign_key: 'to_id'
end
class MailMessage < ActiveRecord::Base
belongs_to :user
emd
I did create a controller for these messages:
class ReceivedEmailsController < ApplicationController
before_filter :authenticate_user!
def index
messages = current_user.received_messages
if messages
render json: messages, each_serializer: MailMessageSerializer, status: :ok
else
render json: :nothing, status: :not_found
end
end
def show
message = current_user.received_messages.where(id: params[:id]).first
if message
render json: message, serializer: MailMessageSerializer, status: :ok
else
render json: :nothing, status: :not_found
end
end
end
The tests:
describe ReceivedEmailsController do
let!(:main_user) { Fabricate :user }
let!(:receiving_user) { Fabricate :user }
let!(:message) { Fabricate :mail_message, user_id: main_user.id, to_id: receiving_user.id }
describe 'get index' do
it 'should give not found when there are no emails received' do
main_user.confirm!
sign_in main_user
get :index
JSON.parse(response.body)['received_emails'].size.should eq 0
end
end
end
Instead of 0 I get 1 back, the same message that I get back when doing the test via the receiving_user. I want to get the message back via the receiving_user but not via the main_user.
I'm not sure whether it can be done this way, whether to use scopes, or even something else I'm not aware of.
Thanks.
One of your let! statements fabricates an email which belongs to main_user, so you get, correctly one result.
UPDATE
The above is incorrect. Try if messages.any? instead of if messages as it will always return true, since associations returns an array (empty array if there are no matching records)
Related
I am trying to send one post to a create in a controller and create two objects. For some reason the contoller only will create a company. I did a similair thing in sinatra and it worked. I know that the route is correct and so is the object the post sends.
Conrtoller:
def index
stocks = current_user.stocks
render json: stocks, include: ['company']
end
def create
company = Comapny.find_or_create_by(name: params["company"])
stock = current_user.stocks.create(stock_params)
render json: stock, status: :created
end
Params:
def stock_params
params.require(:stock).permit(:name, :price_purchased_at, :number, :info, :restaurant )
end
Serializer:
class StockSerializer < ActiveModel::Serializer
attributes :id, :name, :price_purchased_at, :info, :number, :company
belongs_to :user
belongs_to :company
end
I have tried changing the serializer and the params. I have also tried leaving out the company create line to see if it will create the stock but it still won't create a stock.
You ensure in your create that a company with the expected name exists. But you do not pass the found company to the stock creation method. Therefore, the stock creation fails with Company must exist.
Just change your create method to this:
def create
company = Company.find_or_create_by(name: params['company'])
stock = current_user.stocks.create!(stock_params.merge(company_id: company.id))
render json: stock, status: :created
end
I just built a Rails 5 new app --api. I scaffolded a model and added an enum.
class Track < ApplicationRecord
enum surface_type: [:asphalt, :gravel, :snow], _prefix: true
end
One of the scaffolded controller test looks like this:
context "with invalid params" do
it "assigns a newly created but unsaved track as #track" do
post :create, params: {track: invalid_attributes}, session: valid_session
expect(assigns(:track)).to be_a_new(Track)
end
end
I added invalid attributes at the top:
let(:invalid_attributes) {{
"name": "Brands Hatch",
"surface_type": "wood"
}}
and changed the expect line to this
expect(assigns(:track)).not_to be_valid
But the test does not work, because its not possible to create a Track object if you pass an invalid enum.
Controller action:
def create
#track = Track.new(track_params)
if #track.save
render json: #track, status: :created
else
render json: #track.errors, status: :unprocessable_entity
end
end
So how do I test this scenario?
One way you could trap an invalid :surface_type through normal validation is by intercepting the assignment.
class Track < ApplicationRecord
enum surface_type: [:asphalt, :gravel, :snow], _prefix: true
attr_accessor :bad_surface_type
validate :check_surface_type
def surface_type=(surface)
super surface
rescue
self.bad_surface_type = surface
super nil
end
private
def check_surface_type
errors.add(:surface_type, "the value #{bad_surface_type} is not valid") if bad_surface_type
end
end
I have 2 non database attributes in my model. If one of them has a value, I need to return the other one in the json response:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def attributes
if !self.track.nil?
super.merge('max_speed_on_track' => self.max_speed_on_track)
end
end
end
The problem is that the line 'if !self.track.nil?' throws an error when the controller tries to return the json
Perhaps there is a better way as I read that using attr_accessor is a code smell.
What I am trying to do is if the user passes me a track value as a query parameter, then I pass that value to the model and it uses it to calculate the max_speed_on_track, and return that value.
Obviously if no track is provided by the user then I don't want to return max_speed_on_track in the json.
The controller method is very basic for now (I still need to add the code that checks for the track param). The code throws the error on the save line.
def create
#car = Car.new(car_params)
if #car.save
render json: #car, status: :created
else
render json: #car.errors, status: :unprocessable_entity
end
end
Try this out:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def as_json(options = {})
if track.present?
options.merge!(include: [:max_speed_on_track])
end
super(options)
end
end
Since Rails uses the attributes method, and you're only needing this for json output, you can override the as_json method just like in this article. This will allow you to include your max_speed_on_track method in your json output when the track is present (not nil).
I am trying to create a web API that allows creation of FriendShip by email or phone_number.
class Api::FriendshipsController < Api::BaseController
respond_to :json
def create
friend = User.where("email = ? OR phone_number = ?", params[:emailOrPhone], params[:emailOrPhone]).first # create a friend by email or phone_number
if friend.valid? # check if the friend exists, if it does we create our new friendship
friendship = Friendship.new
friendship.user = current_user
friendship.friend = friend
if friendship.valid? # check if friendship is valid
friendship.save # if it is, we save and return a success JSON response
render json: {created: true}, status: 200
else # if it's not a valid friendship, we display a error JSON response
render json: {created: false}, status: 400
end
end
end
end
Here is my FriendShip model
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User"
validates_uniqueness_of :user_id, scope: :friend_id, :message => '%{friend_id} is already a friend'
validate :check_friend_and_user # prevent user trying to add him/herself as friend.
def check_friend_and_user
errors.add(:friend, "can't be the same as user") if user == friend
end
end
Whenever the uniqueness constrain is violated, an error missing interpolation argument :friend_id in "%{friend_id} is already a friend" ({:model=>"Friendship", :attribute=>"User", :value=>2} given) with error code 500
How do I make it not throw an error but instead proceed to give back a 'fail json response' with status code 400
I want the caller of this API to know that they are trying to add someone that is already a friend. Getting back a status code 500 and a bunch of html does not seems to uniquely identify it. So I would like to throw a error in the form of JSON and status 200
What you seem to be trying to do is determine if the friend is already associated to the User via the friendship class. Which you can simplify with a has_many :friendships association on the User object.
Also, the way you're looking up by email OR phone is IMO unnecessarily ambiguous and will be problematic if you want to separately track one or the other for other purposes. Which you seem to be wanting to do since you have them broken out into separate database columns. I think you could just put two form inputs email or phone number and only pass one along to the controller. If you must have only one then you could determine what the form submits in Javascript or something.
In that case, you'd be better off sending the type of identifier with your initial data from your form, so you can look for one or the other. So your form would explicitly send the column lookup identifier, e.g. the params would be equivalent to the ruby hash
{friendship: {email: "someone#somewhere.com"}}
With that in the params then you could do what your trying with this code
# assuming you're passing via a params hash that would look like
# one or the other of the following
# {friendship: {email: "john#doe.com"}}
# {friendship: {phone_number: "123-123-1234"}}
def create
if current_user.friendships.find_or_create_by(friendship_params)
render json: {created: true}, status: 200
else # if it's not a valid friendship, we display a error JSON response
render json: {created: false}, status: 400
end
end
protected
def friendship_params
require(:friendship).permit(:email, :phone_number)
end
I'm doing an update of a form. I can't add my validation in my model for x reason, so I'm adding an error in my projects_controller in the method update. When I update it should raise the error and render :edit but it doesn't. Here is my method
def update
#project = Project.find(params[:id])
#stuff to update
#add error if no legal_media checked, unless if creative upload its own conditions
unless has_media?(#project.legal_option.authorized_format)
#project.legal_option.authorized_format.errors[:base] << "error message"
end
if #project.update_attributes(project_params)
redirect_to brief_path(#project.order.brief)
else
render :edit
end
end
the method has_media? returns false dans when I type #project.legal_option.authorized_format.errors[:base]I have my error message ["error message"].
But when I type #project.legal_option.authorized_format.valid?, it returns true
Any idea how I could make my method raise this error?
Thank you!
UPDATE trying to do the validation in the model :
Since the beginning I want to check that if my column custom_document in legal_option isn't nil (therefore the user uploaded it in the update method of the projects_controller), then, check if there is at least one media in legal_media.
Here are my models :
class LegalOption < ActiveRecord::Base
belongs_to :project
has_one :authorized_format, class_name: "LegalMedia", foreign_key: "legal_option_id"
accepts_nested_attributes_for :authorized_format
has_attached_file :custom_document
validates_attachment :custom_document, content_type: { content_type: "application/pdf" }
end
class LegalMedia < ActiveRecord::Base
belongs_to :legal_option
def self.formats
{all_media: "Tous Media", internet: "Internet", paper: "Presse papier", object: "Objets", television: "TV", radio: "Radio", cinema: "Cinéma", poster_campaign: "Affiches", :press_relation => "Relations Presse", :plv => "Publicité sur lieux de vente", :event => 'Evènementiel'}
end
end
When I did the validation in the beginning with a validate :has_media? My LegalOption.LegalMedia because legal_option_id is nil in legal_media
in the unless block, put the line:
render :edit and return
like:
unless has_media?(#project.legal_option.authorized_format)
#project.legal_option.authorized_format.errors[:base] << "error message"
render :edit and return
end
You should add a validation to the model in order for the valid? to do what you are looking for it to do.
If you look at the docs here, you'll see that valid? just runs all the validations. It doesn't check for any errors that you manually add to the object.
Rails convention dictates that validations shouldn't be implemented in the controller but rather in the model. More specifically, update_attributes just runs valid? after assigning the attributes, which itself just runs validations defined on the model. Any errors already on the model are cleared out beforehand.
If you re-write this as a custom validation on the model, update_attributes should behave as you expect:
class Project < ActiveRecord::Base
validate :legal_option_has_media
private
def legal_option_has_media
unless has_media? legal_option.authorized_format
errors.add :base, "error message"
end
end
end