Taking params[:id] into a .json file with Rails - ruby-on-rails

I'm currently using the Full Calendar gem for Rails, and all is working perfectly. However I would like to dynamically display calendar events based on the :id in the address bar via routes in Ruby on Rails.
Here's my index.json.jbuilder file
json.array!(#appointments.all.where(:user_id => params[:id])) do |appointment|
json.extract! appointment, :id, :user_id, :business_title, :address
json.user appointment.user_id
json.title appointment.business_title
json.start appointment.starts_at
json.className 'boobs'
json.end appointment.starts_at + 30.minutes
json.url appointment_url(appointment, format: :html)
end
This works when the params[:id] is replaced with an existing user id from my database. The url thats generating this is localhost:3000/c/2, so this should display all the events that have been created/or belong to the user with the ID of 2. Here's my Routes:
get 'calendar/:id' => 'users#calendar'

Inside your calendar action, write:
#id = params[:id]
Then in your jbuilder file, write:
json.array!(#appointments.all.where(:user_id => #id)) do |appointment|
I guess that means, you have this in your calendar action:
#appointments = Appointment
...which makes me wonder: why aren't you doing the following in your calendar action:
#appointments = Appointment.all.where(:user_id => params[:id])
...and then in your jbuilder file:
json.array! #appointments do |appointment|
...
...
end
Response to comment:
Suppose you set up your controller like this:
class UsersController < ApplicationController
def calendar
#user_id = params[:user_id] #route: get 'users/calendar/:user_id', to: 'users#calendar'
#By default, render()'s calendar.html.erb
end
def feed
#appointments = Appointment.all.where(user_id: #user_id)
#For the request: http://localhost:3000/users/feed.json, by
#default render()'s /app/views/users/feed.json.jbuilder
end
end
..then you request your calendar() action like this:
http://localhost:3000/users/calendar/2
The problem is: once your rails app is done responding to a request, all the variables inside an action are destroyed, so #user_id, which was assigned the value of params[:user_id], i.e. 2, will be destroyed. As a result, when your calendar sends a request to the feed() action here:
<script>
$(document).ready(function() {
// page is now ready, initialize the calendar...
$('#calendar').fullCalendar({
events: '/users/feed.json' //****HERE****
})
});
</script>
...you won't be able to access #user_id inside the feed() action.
And, because the calendar was never given the user_id, the calendar cannot include it in the url when the calendar sends a request to /users/feed.json.
The stateless nature of the web has been a thorn in the side of developers for a long time, so they have invented ways to make data persist between requests using hidden form fields, cookies, sessions, local storage, databases, etc.
Rail's provides a very simple sessions mechanism, which you can use like this:
#route: get 'users/calendar/:user_id', to: 'users#calendar'
def calendar
session[:user_id] = params[:user_id] #Store the value in the session hash.
end
def feed
#appointments = Appointment.all.where(user_id: session[:user_id]) #Retrieve the value from the session hash.
end
However, you should read about the potential problems when using sessions.
Finally, as far as I can tell your jbuilder page responds with a lot of data that is irrelevant. According to the fullcalendar docs, an Event only requires a :title and the start time :starts_at. But, your jbuilder file provides additional data that is not even used by an Event:
:id
:user_id
:address
And, you actually provide the :user_id data twice, once here:
json.extract! appointment, :id, :user_id, :business_title, :address
and the second time aliased under a different name, user:
json.user appointment.user_id
It looks to me like, the following is all you need:
json.array! #appointments do |appointment|
json.title appointment.business_title
json.start appointment.starts_at
json.className 'boobs'
json.end appointment.starts_at + 30.minutes
json.url appointment_url(appointment, format: :html)
end
...which produces json like this;
[
{"title":"XYZ",
"start":"2014-12-25T12:30:00.000Z",
"className":"boobs",
"end":"2014-12-25T13:00:00.000Z",
"url":"http://localhost:3000/appointments/1.html"},
{"title":"ABC",
"start":"2015-01-25T09:30:00.000Z",
"className":"boobs",
"end":"2015-01-25T10:00:00.000Z",
"url":"http://localhost:3000/appointments/2.html"},
{"title":"TTT",
"start":"2015-01-03T14:30:00.000Z",
"className":"boobs",
"end":"2015-01-03T15:00:00.000Z",
"url":"http://localhost:3000/appointments/3.html"},
]

Related

Rails mass create from array of hash

I get params like this: Parameters: {"utf8"=>"✓", "events"=>{"363"=>{"name"=>"3"}}, "new_events"=>[{"name"=>"1"}, {"name"=>"2"}], "date"=>"2016-11-01"}
What I would like to do is create Events from new_events array of hashes, but it doesn's let me because it neets to be sanitized for mass assignment.
This is how event_params looks like:
def event_params
params.require(:events).permit(:id, :name, :start_time, :user_id)
end
This setup is because I want to update and/or create new records when I press submit.
The current way I do this is
params[:new_events].each_with_object({}) do |e|
Event.create(name: e[:name], user_id: current_user.id, start_time: params[:date])
end
But Im not sure it's the correct way to do it
You should be able to sanitize the array of parameters:
params.require(:events).permit(new_events: [:name])
Event.create(params[:new_events] do |new_event|
new_event.user = current_user
end
But note that you'll have to loop over the objects anyway, so it doesn't make much of a difference. You may want to just do the loop with Event.new and validate them as a group:
events = Event.new(params[:new_events]) do |new_event|
new_event.user = current_user
...
end
if events.any?(&:invalid?)
[error handling]
else
events.save!

Rails server raising "recycled object" error sometimes when instances object by object_id

My problem it happens when adds part of form dynamically with ajax in the way Rails does. The asynchronous request is made when the event input of a Node object happens, and it's work. She sends it stock_ingredient_f.object_id defined in data-action attribute to controller and a helper method form_parent is defined to be accessed per the partial _stock_ingredient_fields.haml.
This is my form.
= form_for #stock_ingredient, remote: true do |stock_ingredient_f|
.field
= stock_ingredient_f.label "Enable/disable keyboard"
= check_box :keyboard, :handler
.field
= stock_ingredient_f.label "Barcode"
= text_field :ingredient, :barcode, value: stock_ingredient_f.object.ingredient.try(:barcode), class: "barcode", :'data-action' => ingredient_async_path(form_parent_object_id: stock_ingredient_f.object_id)
- if #stock_ingredient.stock.present?
.field
= stock_ingredient_f.label "Stock"
= stock_ingredient_f.number_field :stock
%span.brown-color
= "(#{#stock_ingredient.ingredient.net_weight_unit})"
This is a partial that should be load dynamically.
.ingredient-data
.field
%dl
.line
%dt
Ingredient:
%dd
= "#{#ingredient.name}"
.line
%dt
Stock:
%dd.stock
= "#{MeasurementUnits.humanize_for #ingredient.stock_ingredient.stock, resolve_unit_type(#ingredient.net_weight_unit)}"
.field
/ the problem is raised here
= form_parent.label "Stock"
= form_parent.number_field :stock
%span.brown-color
= "(#{#ingredient.net_weight_unit})"
.field
= form_parent.submit "Save"
= form_parent.button "Cancel", type: :reset
This is my application_controller.rb, where is defined the method form_parent
class ApplicationController < ActionController::Base
respond_to :html, :js
# Prevent CSRF attacks by raising an exception.
# protect_from_forgery with: :exception
# when will has it login, change this line
protect_from_forgery with: :exception, if: Proc.new { |c| c.request.format != 'application/json' }
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
def form_parent
ObjectSpace._id2ref(params[:form_parent_object_id].to_i) if params[:form_parent_object_id]
end
helper_method :form_parent
def root
render "layouts/application", layout: false
end
protected
def id
params[:id]
end
def redirect_for_async_request location
render js: "window.location='#{location}'"
end
def alert message
render js: "window.alert('#{message}')"
end
# def no_cache
# if Rails.env.development?
# response.headers["Cache-Control"] = "no-cache, must-revalidate, max-age=0"
# end
# end
end
When the page is refreshed and the request is fired again, the server says: ActionView::Template::Error (0x002ac5927b9058 is recycled object) probably does reference to response of ObjectSpace._id2ref.
I belive that the garbage collector is involved.
UPDATE application_controller.rb
def form_parent
##form_parent
end
def store_form object
##form_parent = object
end
helper_method :form_parent, :store_form
form
= form_for #stock_ingredient, remote: true do |stock_ingredient_f|
- store_form stock_ingredient_f
.field
= stock_ingredient_f.label "Enable/disable keyboard"
= check_box :keyboard, :handler
.field
= stock_ingredient_f.label "Barcode"
= text_field :ingredient, :barcode, value: stock_ingredient_f.object.ingredient.try(:barcode), class: "barcode", :'data-action' => ingredient_async_path
- if #stock_ingredient.stock.present?
.field
= stock_ingredient_f.label "Stock"
= stock_ingredient_f.number_field :stock
%span.brown-color
= "(#{#stock_ingredient.ingredient.net_weight_unit})"
This error means you are trying to access an object that has been garbage collected, as you may have suspected. This happened because the object id was from a previous time, and once the id was sent to the browser, no more references to that object existed, so ruby cleaned up that object.
Although it may not always be true, it's best to think of each request as running in a new, separate ruby process. If you want to 'pass' data between requests, you have to either send it to the client (if you don't mind the data being changed) or store it in a session, or store it in a database. You cannot pass around objects the way you do here.
You suspect correctly - the object with that object_id has been garbage collected. I'm not sure why you are doing this, but it sounds like a really bad idea. It's not completely clear to me what form_parent is (a FormBuilder instance ?) but at a guess you should be passing the id of an object in the database, fetching the object from the database and using that to get a fresh FormBuilder instance from form_for or fields_for.
Even if you can make your current approach work it would limit you to a single rails instance, whereas any serious deployment of an application uses multiple instances spread across 2 or more servers.

Rails CSV upload to update records - attribute not saved to db

I have a system for updating InventoryItem records using a CSV upload.
I have this controller method:
def import
InventoryItem.import(params[:file], params[:store_id])
redirect_to vendors_dashboard_path, notice: "Inventory Imported."
end
Which of course calls this model method:
def self.import(file, store_id)
CSV.foreach(file.path, headers: true) do |row|
inventory_item = InventoryItem.find_or_initialize_by_code_and_store_id(row[0], store_id)
inventory_item.update_attributes(:price => row.to_hash.slice(:price))
end
end
I want to update only the :price attribute in the update because and :code and :store_id won't change. Currently the records being imported have price all as 0.0 (big decimal). Not nil, or the correct value, but 0.0, so clearly I'm doing something wrong to make this work. I know when I do this in the console it looks something like this:
inventory_item = InventoryItem.find_by_id(1)
inventory_item.update_attributes(:price => 29.99)
Any ideas on why I'm not updating the price attribute correctly?
Trying this, it doesn't seem like csv returns symbolized hash keys
and slice doesn't seem to work there. how about this inside your
CSV.foreach loop:
inventory_item.update_attributes(:price => row.to_hash["price"])

Make API Request, puts response in RAILS 4.0.0

I have been using ruby to make API calls and operating strictly in the terminal for some time. I am now in the process of learning more about rails and trying to get out of my terminal. How can I, using rails 4.0, put a variable to the screen from an already existing .rb file? I am confused as to where I should write the API request to get the variable- Is it a controller, can I write it directly in a view, etc.
Sample idea:
#test.rb
call= "/api/v2/surveys/"
auth = {:username => "test", :password => "password"}
url = HTTParty.get("https://surveys.com#{call}",
:basic_auth => auth,
:headers => { 'ContentType' => 'application/json' } )
response = JSON.parse(url.body)
survey_ids = response["surveys"].map { |s| s["id"] }
survey_ids.each do |i|
puts i
end
That is a sample .rb script I already have. The difference is I would like for puts i to happen on a web app when a page is loaded instead of me running the script in my terminal. What would I use in rails to make that happen?
It depends entirely on how your application is going to be set up but here's a basic example:
Say you have a Survey model:
class Survey < ActiveRecord::Base
attr_accessible :survey_id
end
You can place your call for a list of surveys (I'm assuming that's what your code does) in the SurveysController:
class SurveysController < ApplicationController
def index
#surveys = Survey.all
end
def show
#survey = Survey.find(params[:id])
end
def pull_surveys
call= "/api/v2/surveys/"
auth = {:username => "test", :password => "password"}
url = HTTParty.get("https://surveys.com#{call}",
:basic_auth => auth,
:headers => { 'ContentType' => 'application/json' } )
response = JSON.parse(url.body)
survey_ids = response["surveys"].map { |s| s["id"] }
survey_ids.each do |i|
Survey.create(survey_id: i)
end
end
After calling the pull_surveys method, you'll actually have surveys your view can load so in your views for the Survey Model you can use #surveys or #survey (depending on which view you're in) and serve up whatever you want (e.g #survey.survey_id in show to show that specific survey's ID).
Note that you'll want to be careful about where you place your API call methods - I placed it in the controller for simplicity's sake but you may not want to do this.
There's lots of useful info in the rails guides to get you started: http://guides.rubyonrails.org/index.html

Correct way to handle multiparameter attributes corresponding to virtual attributes

I have a Rails app with a model containing a birthdate attribute. This corresponds to a column in my database defined using the ActiveRecord date type. With this I am able to use the date_select form helper method to render this as a three-select input in my view. The form parameters corresponding to this field are then serialized back to the controller as birthdate(1i), birthdate(2i) and birthdate(3i). Consequently, I can use the standard update_attributes method within my controller on my model to update all fields on my model.
I'm now experimenting with encrypting this field using the attr_encrypted gem. While the gem supports marshalling (this is nice), there is no longer a real column of name birthdate of type date - instead, attr_encrypted exposes the value as a virtual attribute birthdate backed by a real encrypted_birthdate column. This means that update_attributes is unable to perform the previous multiparameter attribute assignment to populate and save this column. Instead, I get a MultiparameterAssignmentErrors error resulting from the call to the internal column_for_attribute method returning nil for this column (from somewhere within execute_callstack_for_multiparameter_attributes).
I'm currently working around this as follows:
My model in app/models/person.rb:
class Person < ActiveRecord::Base
attr_encrypted :birthdate
end
My controller in app/controllers/people_controller.rb:
class PeopleController < ApplicationController
def update
# This is the bit I would like to avoid having to do.
params[:person] = munge_params(params[:person])
respond_to do |format|
if #person.update_attributes(params[:person])
format.html { redirect_to #person, notice: 'Person was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #person.errors, status: :unprocessable_entity }
end
end
end
private
def munge_params(params)
# This separates the "birthdate" parameters from the other parameters in the request.
birthdate_params, munged_params = extract_multiparameter_attribute(params, :birthdate)
# Now we place a scalar "birthdate" where the multiparameter attribute used to be.
munged_params['birthdate'] = Date.new(
birthdate_params[1],
birthdate_params[2],
birthdate_params[3]
)
munged_params
end
def extract_multiparameter_attribute(params, name)
# This is sample code for demonstration purposes only and currently
# only handles the integer "i" type.
regex = /^#{Regexp.quote(name.to_s)}\((\d+)i)\)$/
attribute_params, other_params = params.segment { |k, v| k =~ regex }
attribute_params2 = Hash[attribute_params.collect do |key, value|
key =~ regex or raise RuntimeError.new("Invalid key \"#{key}\"")
index = Integer($1)
[index, Integer(value)]
end]
[attribute_params2, other_params]
end
def segment(hash, &discriminator)
hash.to_a.partition(&discriminator).map do |a|
a.each_with_object(Hash.new) { |e, h| h[e.first] = e.last }
end
end
end
And my view app/views/people/_form.html.erb:
<%= form_for #person do |f| %>
<%= f.label :birthdate %>
<%= f.date_select :birthdate %>
<% f.submit %>
<% end %>
What's the proper way to handle this type of attribute without having to introduce ad hoc munging of the params array like this?
Update:
Looks like this might refer to a related problem. And this too.
Another update:
Here is my current solution, based on Chris Heald's answer. This code should be added to the Person model class:
class EncryptedAttributeClassWrapper
attr_reader :klass
def initialize(klass); #klass = klass; end
end
# TODO: Modify attr_encrypted to take a :class option in order
# to populate this hash.
ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS = {
:birthdate => EncryptedAttributeClassWrapper.new(Date)
}
def column_for_attribute(attribute)
attribute_sym = attribute.to_sym
if encrypted = self.class.encrypted_attributes[attribute_sym]
column_info = ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS[attribute_sym]
column_info ||= super encrypted[:attribute]
column_info
else
super
end
end
This solution works as is, but would be even better if attr_encrypted were to take a :class option that would construct the ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS hash dynamically. I'm going to look at ways I can extend/monkeypatch attr_encrypted to do this. Gist available here: https://gist.github.com/rcook/5992293.
You can monkeypatch your model to pass through column_for_attribute calls. I haven't tested this, but it should cause reflection on the birthday field to instead return the reflection for the encrypted_birthday field, which should cause multiparam attributes to properly assign (as AR will then be able to infer the field type):
def column_for_attribute(attribute)
if encrypted = encrypted_attributes[attribute.to_sym]
super encrypted[:attribute]
else
super
end
end
We're patching column_for_attribute per this line so that AR can infer the proper type for the column. It needs to figure out that parameters for "birthday" should be a DateTime type of whatnot, and can't infer that from a virtual attribute. Mapping the reflection onto the actual column should resolve that.

Resources