I have two tables business_hours and working_hours.
The models:
class BusinessHour < ApplicationRecord
has_many :working_hours, class_name: "WorkingHour"
belongs_to :organization
accepts_nested_attributes_for(:working_hours, update_only: true)
end
class WorkingHour < ApplicationRecord
belongs_to :business_hour
validates :day, inclusion: { in: %w(mon tue wed thu fri sat sun) }
validate :validate_day, on: :create
def validate_day
if business_hour.working_hours.where(day: self.day).exists?
errors.add(:day, "has already been added")
end
end
end
The controller:
class Api::V1::Admin::BusinessHoursController < Api::BaseController
def update
#organization.build_business_hours unless #organization.business_hours
if #organization.business_hours.update(business_hour_params)
render status: :ok,
json: { notice: I18n.t("resource.update", resource_name: "Organization") }
else
render status: :unprocessable_entity, json: { errors: #organization.business_hours.errors.full_messages }
end
end
private
def business_hour_params
params.require(:business_hours).permit(
:enabled, :away_message, working_hours_attributes: [:day, :start_time, :end_time]
)
end
end
When the business_hours is updated , I'm trying to update the working_hours as well.
The required behaviour is that, the working_hours should be created with day field from mon to friday and each business_hour will have 7 working_hours entry. For example, if a working_hour with day as "mon" already exists for a business_hour, when the update method in controller is called , only the start_time and end_time needs to be updated for the particular working_hour. How to go about this?
Request body example:
{
"business_hours": {
"enabled": true,
"away_message": "Hello",
"working_hours_attributes": [{
"day": "mon",
"start_time": "Tue, 06 Sep 2022 10:07:21.771116000 UTC +00:00",
"end_time": "Tue, 06 Sep 2022 10:07:21.771116000 UTC +00:00"
}]
}
}
As said on the comment:
Well, nested attributes can be updated by using "id" key, if your frontend doesn't have this info you could treat the parameters to convert day: 'mon' to id: id by storing your 'working_hours' into a key-value pair, ..., I don´t think there is a better way of doing it without using this middleware between your update and your permitted parameters
class Api::V1::Admin::BusinessHoursController < Api::BaseController
def update
#organization.build_business_hours unless #organization.business_hours
if #organization.business_hours.update(update_business_hour_params)
render status: :ok,
json: { notice: I18n.t("resource.update", resource_name: "Organization") }
else
render status: :unprocessable_entity, json: { errors: #organization.business_hours.errors.full_messages }
end
end
private
def business_hour_params
params.require(:business_hours).permit(
:enabled, :away_message, working_hours_attributes: [:day, :start_time, :end_time]
)
end
# THIS HASN'T BEEN TESTED, USE IT AS AN EXAMPLE
def update_business_hour_params
update_business_hour_params = business_hour_params
update_business_hour_params[:working_hours_attributes].each do |working_hour_parameters|
working_hour_parameters[:id] = working_hours_day_id_pair[working_hour_parameters.delete(:day)] # Retrieves the id from the day
end
update_business_hour_params
end
def working_hours_day_id_pair
#working_hours_day_id_pair ||= #organization.business_hours.working_hours.pluck(:day, :id).to_h
end
end
as said, this is an example, I could not test the code, but that's the idea
as your attributes is already update_only, you should be good to go, hope this helps you
Related
Model convention parameters need to be in English, but the input JSON request keys need to send it in Spanish, how is the best practice for rails to accept parameters in Spanish and save in database?
MODEL:
class Player < ApplicationRecord
validates :name, :level, :goals, :salary, :bonus, :team, presence: true
end
INPUT:
{
"jugadores" : [
{
"nombre":"Snow",
"nivel":"C",
"goles":10,
"sueldo":50000,
"bono":25000,
"sueldo_completo":null,
"equipo":"rojo"
},
{
"nombre":"JC",
"nivel":"A",
"goles":30,
"sueldo":100000,
"bono":30000,
"sueldo_completo":null,
"equipo":"azul"
}
]
}
Controller:
...
def create
#player = Player.new(player_params)
if #player.save
render json: #player, status: :created, location: #player
else
render json: #player.errors, status: :unprocessable_entity
end
end
...
def player_params
params.permit( jugadores: [ :nombre, :nivel, :goles, :salario, :bono, :salario_completo, :equipo ] )
end
...
thnx
Ill tried to translate with I18n, but I can't solve translate ActiveController
$"translation missing: en.{"jugadores"=>{"name"=>"Irving"}}"
Location is basically an address with other information fields. This is my first app and I followed Hartl and others in building it, but ignored failing tests that I couldn't fix at the time. Now I'm trying to rectify that. I've looked at all the postings I found with this problem, but still can't figure it out (discussed at end). The app works, I can create new locations, so I think the error is with the test.
FAIL["test_should_create_location", LocationsControllerTest, 2017-02-28 12:02:08 -0800]
test_should_create_location#LocationsControllerTest (1488312128.31s)
"Location.count" didn't change by 1.
Expected: 4
Actual: 3
test/controllers/locations_controller_test.rb:21:in `block in <class:LocationsControllerTest>'
Edited location_controller_test.rb (The location controller test has 8 tests, this is the one that fails):
require 'test_helper'
class LocationsControllerTest < ActionController::TestCase
setup do
#location = locations(:one)
end
test "should create location" do
assert_difference('Location.count') do
post :create, location: { address: #location.address,
city: #location.city,
state: #location.state,
longitude: #location.longitude,
latitude: #location.latitude,
geom: #location.geom,
coords_not_locked: #location.coords_not_locked,
geocoded_with: #location.geocoded_with,
extant: #location.extant,
current_description: #location.current_description,
source: #location.source,
ref_link: #location.ref_link,
notes: #location.notes }
# debugger
end
assert_redirected_to location_path(assigns(:location))
end
locations_controller.rb:
class LocationsController < ApplicationController
helper_method :sort_column, :sort_direction
before_action :set_location, only: [:show, :edit, :update, :destroy]
def index
#locations = Location.all
# For sortable columns
#locations = Location.order(sort_column + " " + sort_direction)
end
def show
end
def new
#location= Location.new({:address=> "Use W E etc and St without PERIODS"})
repopulateResidResto()
end
def edit
end
def create
# Instantiate a new object using form parameters (notes here from Lynda>Skoglund)
#location = Location.new(location_params)
# Save the object
respond_to do |format|
if #location.save
# If save succeeds, redirect to the index action
format.html { redirect_to #location, notice: 'Address was successfully created.' }
format.json { render :show, status: :created, location: #location}
else
# If save fails, redisplay the form with information user filled in
format.html { render :new }
format.json { render json: #location.errors, status: :unprocessable_entity }
end
end
repopulateResidResto()
end # end create
def update
respond_to do |format|
if #location.update(location_params)
# If update succeeds, redirect to the show page
format.html { redirect_to #location, notice: 'Address was successfully updated.' }
format.json { render :show, status: :ok, location: #location }
else
# If update fails, redisplay the edit form for fixing
format.html { render :edit }
format.json { render json: #location.errors, status: :unprocessable_entity }
end
end
repopulateResidResto()
end # End update
def destroy
location = #location.destroy
respond_to do |format|
format.html { redirect_to locations_url, notice: "Location '#{location}' was successfully destroyed." }
format.json { head :no_content }
end
repopulateResidResto()
end
private
# Use callbacks to share common setup or constraints between actions.
def set_location
#location = Location.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def location_params
params.require(:location).permit(:address, :city, :state, :longitude, :latitude, :geom, :coords_not_locked, :geocoded_with, :extant, :current_description, :source, :ref_link, :notes)
end
def sort_column
Location.column_names.include?(params[:sort]) ? params[:sort] : "address"
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
end
end
locations.yml:
one:
address: 1 Address1
city: Los Angeles
state: California
longitude: 99.99
latitude: 99.99
extant: false
current_description: MyString2
notes: Notes1
source: Source1
geocoded_with: geocoded_with_1
coords_not_locked: false
ref_link: ref_link_1
geom: 0101000020E61000008B187618938F5DC0C2189128B4064140
two:
address: 2 Address2
city: Los Angeles
state: California
longitude: 9.99
latitude: 9.99
extant: true
current_description: MyString
notes: MyString
source: MyString
geocoded_with: MyString
coords_not_locked: true
ref_link: MyString
geom: 0101000020E61000007B4963B48E8F5DC0467C2766BD064140
three:
address: 3 Address3
city: Los Angeles
state: California
longitude: 9.99
latitude: 9.99
extant: true
current_description: MyString
notes: MyString3
source: MyString3
geocoded_with: MyString3
coords_not_locked: true
ref_link: MyString3
geom: 0101000020E61000007B4963B48E8F5DC0467C2766BD064140
The model, location.rb:
class Location < ActiveRecord::Base
has_many :years, dependent: :destroy
has_many :people, through: :years
has_many :resto_resid_lines
def longlat
"#{longitude} #{latitude}"
end
def notes_plus_geocode
if notes == ""
"#{geocoded_with}"
else
"#{notes} #{geocoded_with}"
end
end
def full_address
full_address = "#{address}, #{city}, #{state}"
end
# For next and previous in show.
def next
Location.where(["id > ?", id]).first
end
def previous
Location.where(["id < ?", id]).last
end
geocoded_by :full_address
after_validation :geocode, :if => :coords_not_locked?
validates :address, length: { minimum: 5, maximum: 50 }, uniqueness: true
end
test_helper.rb
require 'simplecov'
SimpleCov.start
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/reporters"
Minitest::Reporters.use!
class ActiveSupport::TestCase
fixtures :all
# Returns true if a test user is logged in.
def is_logged_in?
!session[:user_id].nil?
end
# Logs in a test user.
def log_in_as(user, options = {})
password = options[:password] || 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
post login_path, session: { email: user.email,
password: password,
remember_me: remember_me }
else
session[:user_id] = user.id
end
end
private
# Returns true inside an integration test.
def integration_test?
defined?(post_via_redirect)
end
end
If I turn the debugger on in the test, #location is
(byebug) pp #location
#<Location:0x007ff26ffa1ba8
id: 980190962,
address: "MyString21",
city: "MyString23",
state: "MyString25",
longitude: #<BigDecimal:7ff26ff96b40,'0.9999E2',18(27)>,
latitude: #<BigDecimal:7ff26ff96a50,'0.9999E2',18(27)>,
extant: false,
current_description: "MyString2",
notes: "MyString24",
created_at: Sun, 05 Mar 2017 18:46:12 UTC +00:00,
updated_at: Sun, 05 Mar 2017 18:46:12 UTC +00:00,
geom: "0101000020E61000008B187618938F5DC0C2189128B4064140",
source: "MyString",
geocoded_with: "MyString",
coords_not_locked: false,
ref_link: "MyString">
#<Location id: 980190962, address: "MyString21", city: "MyString23", state: "MyString25", longitude: #<BigDecimal:7ff26ff96b40,'0.9999E2',18(27)>, latitude: #<BigDecimal:7ff26ff96a50,'0.9999E2',18(27)>, extant: false, current_description: "MyString2", notes: "MyString24", created_at: "2017-03-05 18:46:12", updated_at: "2017-03-05 18:46:12", geom: "0101000020E61000008B187618938F5DC0C2189128B4064140", source: "MyString", geocoded_with: "MyString", coords_not_locked: false, ref_link: "MyString"
I'm not sure what to expect for this.
One posting that seemed relevant "User.count" didn't change by 1 - Rails had incomplete yml—I've triple checked mine, but maybe still missing something.
#ArtOfCode. Creating in console (I think this is what you're asking). And since id is nil and it doesn't appear in the database, you may be on the right track:
irb(main):004:0> location = Location.create( address: "1 Address1", city: "Los Angeles", state: "California", longitude: 99.99, latitude: 99.99, extant: false, current_description: "MyString2", notes: "MyString24", source: "MyString", geocoded_with: "MyString", coords_not_locked: false, ref_link: "MyString", geom: "0101000020E61000008B187618938F5DC0C2189128B4064140")
(0.2ms) BEGIN
Location Exists (0.4ms) SELECT 1 AS one FROM "locations" WHERE "locations"."address" = '1 Address1' LIMIT 1
SQL (2.5ms) INSERT INTO "locations" ("address", "state", "longitude", "latitude", "extant", "current_description", "notes", "source", "geocoded_with", "coords_not_locked", "ref_link", "geom", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING "id" [["address", "1 Address1"], ["state", "California"], ["longitude", "99.99"], ["latitude", "99.99"], ["extant", "f"], ["current_description", "MyString2"], ["notes", "MyString24"], ["source", "MyString"], ["geocoded_with", "MyString"], ["coords_not_locked", "f"], ["ref_link", "MyString"], ["geom", "0101000020E61000008B187618938F5DC0C2189128B4064140"], ["created_at", "2017-03-05 20:00:28.246188"], ["updated_at", "2017-03-05 20:00:28.246188"]]
(2.1ms) COMMIT
#<Location:0x007fe851a9bac8> {
:id => 177,
:address => "1 Address1",
:city => "Los Angeles",
:state => "California",
:longitude => 99.99,
:latitude => 99.99,
:extant => false,
:current_description => "MyString2",
:notes => "MyString24",
:created_at => Sun, 05 Mar 2017 20:00:28 UTC +00:00,
:updated_at => Sun, 05 Mar 2017 20:00:28 UTC +00:00,
:geom => "0101000020E61000008B187618938F5DC0C2189128B4064140",
:source => "MyString",
:geocoded_with => "MyString",
:coords_not_locked => false,
:ref_link => "MyString"
}
The application is incomplete, but you can see a location here. Not currently allowing sign-ups, so obviously create can't be used. The addresses are more than 100 years old and the geo coordinates may not be generated. geom is created later in PostGIS.
I imagine there is a simple solution, but it alludes me. gem 'rails' , '4.2.7' and ruby '2.3.1'
Fixtures create database entries automatically. Your location fixture one exists in the database.
So when you try a post to create a new location and you specify...
post :create, location: { address: #location.address,
You are trying to create a location with an address that already exists, but
validates :address, length: { minimum: 5, maximum: 50 }, uniqueness: true
...specifies that the address must be unique, so the record you're attempting to post is not valid because it has the same address as an existing record.
Simply change the address in your post :create call
post :create, location: { address: "1 A New Address",
i have a model places in relationship with the model opening_times.
In my places_controller i have this:
def index
places = Place.all
if places
render json: {
status: :ok,
result: places.as_json(
only: [
:id,
:name,
],
include: [
{ opening_times: {only: [:dayWeek, :open, :close]}},
]
)
}
else
render json: { errors: 'invalid request' }, status: 422
end
end
private
def place_params
params.require(:place).permit(:user_id, :name)
end
The open and close columns in DB are time.
How can i force to return a format time as %H:%M?
I'm not developing on RoR 3+ years... fill free to fix if there is any problem
So can You try this:
places = Place.all.map {|place|
place.opening_times.map! {|opening_time|
opening_time[:open].strftime! "%H:%M"
opening_time[:close].strftime! "%H:%M"
}
}
I am refactoring a project, and I remembered that I had some troubles in realizing how to put a nested object, but I found this question useful.
So, as I understand it, you needed to pass as a parameter your associated model name in plural and add a '_attributes' to it. It worked great in Rails 3.2.13.
Now, here is what I have in Rails 4:
class TripsController < Api::V1::ApiController
def create
begin
#user = User.find(params[:user_id])
begin
#campaign = #user.campaigns.find(params[:campaign_id])
if #trip = #campaign.trips.create(trip_params)
render json: #trip, :include => :events, :status => :ok
else
render json: { :errors => #trip.errors }, :status => :unprocessable_entity
end
rescue ActiveRecord::RecordNotFound
render json: '', :status => :not_found
end
rescue ActiveRecord::RecordNotFound
render json: '', :status => :not_found
end
end
private
def trip_params
params.require(:trip).permit(:evnt_acc_red, :distance, events_attributes: [:event_type_id, :event_level_id, :start_at, :distance])
end
end
And the Trip model looks like this:
class Trip < ActiveRecord::Base
has_many :events
belongs_to :campaign
accepts_nested_attributes_for :events
end
So, I am doing a POST call with the following JSON:
{"trip":{"evnt_acc_red":3, "distance":400}, "events_attributes":[{"distance":300}, {"distance":400}]}
And, even though I don't get any kind of error, no event is being created. The trip is being created correctly, but not the nested object.
Any thoughts on what should I do to make this work on Rails 4?
Alright, so... I was sending the JSON wrongly:
Instead of:
{
"trip": {
"evnt_acc_red": 3,
"distance": 400
},
"events_attributes": [
{
"distance": 300
},
{
"distance": 400
}
]
}
I should have been sending:
{
"trip": {
"evnt_acc_red": 3,
"distance": 400,
"events_attributes": [
{
"distance": 300
},
{
"distance": 400
}
]
}
}
I have a model for languages and i want to get all the languages as json but the json output looks as follows
[{"language":{"created_at":null,"id":1,"language":"English","updated_at":null}},{"language":{"created_at":null,"id":2,"language":"Swedish","updated_at":null}},{"language":{"created_at":null,"id":3,"language":"German","updated_at":null}},{"language":{"created_at":null,"id":4,"language":"French","updated_at":null}},{"language":{"created_at":null,"id":5,"language":"spanish","updated_at":null}},{"language":{"created_at":null,"id":6,"language":"dutch","updated_at":null}},{"language":{"created_at":"2012-12-03T05:01:18Z","id":7,"language":"Tamil","updated_at":"2012-12-03T05:01:18Z"}}]
but i want to make this as
{"language":[{"created_at":null,"id":1,"language":"English","updated_at":null},{"created_at":null,"id":2,"language":"Swedish","updated_at":null},{"created_at":null,"id":3,"language":"German","updated_at":null},{"created_at":null,"id":4,"language":"French","updated_at":null},{"created_at":null,"id":5,"language":"spanish","updated_at":null},{"created_at":null,"id":6,"language":"dutch","updated_at":null},{"created_at":null,"id":7,"language":"Tamil","updated_at":null} ] }
Update
def index
#languages = Language.all
respond_to do |format|
format.json { render json: #languages}
end
end
update 2
class Language < ActiveRecord::Base
ActiveRecord::Base.include_root_in_json = false
has_and_belongs_to_many :users
end
I believe this should work:
format.json { render json: { "language" => #languages.as_json(:root => false) }.to_json }
What this does it to convert the #languages array into an array of JSON-formatted hash models with no root keys (using as_json), then wraps the result in a hash with a root key "language", and convert that hash into a JSON-formatted string with to_json. (See the docs for details on including or not including a root node using as_json.)
For example, with a model Post:
posts = Post.all
#=> [#<Post id: 1, name: "foo", title: "jkl", content: "some content", created_at: "2012-11-22 01:05:46", updated_at: "2012-11-22 01:05:46">]
# convert to array of hashes with no root keys
posts.as_json(root: false)
#=> [{"content"=>"some content", "created_at"=>Thu, 22 Nov 2012 01:05:46 UTC +00:00, "id"=>1, "name"=>"foo", "title"=>"jkl", "updated_at"=>Thu, 22 Nov 2012 01:05:46 UTC +00:00}]
# add root back to collection:
{ "post" => posts.as_json(root: false) }
#=> {"post"=>[{"content"=>"some content", "created_at"=>Thu, 22 Nov 2012 01:05:46 UTC +00:00, "id"=>1, "name"=>"foo", "title"=>"jkl", "updated_at"=>Mon, 03 Dec 2012 09:41:42 UTC +00:00}]}
# convert to JSON-formatted string
{ "post" => posts.as_json(root: false) }.to_json
#=> "{\"post\":[{\"content\":\"some content\",\"created_at\":\"2012-11-22T01:05:46Z\",\"id\":1,\"name\":\"foo\",\"title\":\"jkl\",\"updated_at\":\"2012-12-03T09:43:37Z\"}]}"
override the as_json on the Model you want to customize
def as_json options={}
{
id: id,
login: login,
name: custom.value, #for custom name
...
}
end
==> or
def as_json(options={})
super(:only => [:id, :login, :name ....])
end
from : here
Other link: here
I suggest you to use rabl gem (https://github.com/nesquena/rabl) to format your data.
Override as_json method in your model, to include associations, hide columns and why not? calling custom methods as they were attributes
def as_json(options={})
super(:except => [:created_at,:updated_at],
:include => {
:members => {
:only => [:role, :account],
:include => {
:account => {
:only => [:name, :subdomain]
}
}
}
},
:methods => [:jwt_token]
)
end
This will output something like this:
{
"id": 2,
"name": "Test Teacher",
"email": "teacher#testing.io",
"jwt_token":"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MiwiZXhwIjoxNTY2NzQ0OTQzfQ.HDGu7JiJEQEEpGo7inuXtOZBVQOfTaFquy8dr-QH5jY",
"members": [{
"role": "instructor",
"account": {
"name": "Testing",
"subdomain": "test"
}
}],
}
The easiest way of adding custom json output when you render json is by using gem that provide many json templates-
https://github.com/fabrik42/acts_as_api