ruby on rails, search results to json array (jbuilder) - ruby

I'm new to rails and I just have a quick question...
I have a calendar gem which currently pulls event data from json as such (viewings/index.json.jbuilder)
json.array!(#viewings) do |viewing|
json.extract! viewing, :id, :room_id, :user_id, :start_time, :end_time, :notes
json.title viewing.user.name
json.start viewing.start_time
json.end viewing.end_time
json.url viewing_url(viewing, format: :html)
end
anyway, I have created a search feature on my "viewings" index page, where a user selects the room from a drop-down and it displays the corresponding viewings.
However, I need to make a JSON array from the results of the search, so it can be fed to the calendar.
I know I can achieve this by doing something like, which does work:
#viewings = Viewing.where(room_id: 1 )
but obviously I want to pass a variable in to that line of code, something like
#viewings = Viewing.where(room_id: params[:search_string] )
The search string will always be a room ID, but the above code (and any variations I can think of) don't seem to work.
OR can I make a JSON array from my search output. my search is as so...
def search
#rooms = Room.all
#viewings = Viewing.simple_search(params[:search_string])
render :action => "index"
end
Can anyone help?!
EDIT:
I'm still struggling with this one a lot. I had a look at the jquery page below but its still not doing what I hoped.
I have simplified the function in my viewings controller as so:
def rmselect
#query1 = params[:rmno]
#rooms = Room.all
#viewings=Room.find(params[:rmno]).viewings
render :action => "index"
end
And I have created an _rmselect.json.jbuilder as so:
#viewings = Viewing.where(room_id: #query1 )
json.array!(#viewings) do |viewing|
json.title viewing.user.name
json.start viewing.start_time
json.end viewing.end_time
end
I was hoping the #query1 variable would be passed into the jbuilder page which would cause it only to display viewings for the selected room. However, it seems that jbuilder cannot see any variables unless they are in def index (where it's no good).
I also can't understand why _rmselect.json.jbuilder is taking the #viewings.all from def index, rather then the #viewings=Room.find(params[:rmno]).viewings from def rmselect.

Ok, I suppose the situation is like that:
Viewing model:
class Viewing < ActiveRecord::Base
...
belongs_to :room
...
end
Room model:
class Room < ActiveRecord::Base
...
has_many :viewings
...
end
Retrive viewings from room id, you can do something like:
#viewings=Room.find(params[:search_string]).viewings
For create JSON array of result, render jbuilder view who contains something like that:
# #people = People.all
json.array! #people, :id, :name
# => [ { "id": 1, "name": "David" }, { "id": 2, "name": "Jamie" } ]
I report an example of official jbuilder docs I think you can use json.array with viewings:
json.array! #viewings, field1, field2, ...
Otherwise you can use this in your controller:
render json: #viewings
but if you've used jbuilder, I think it's better you continue that way.

Related

How to query multiple fields with Chewy

Let's say I have an index with multiple objects in it:
class ThingsIndex < Chewy::Index
define_type User do
field :full_name
end
define_type Post do
field :title
end
end
How do I search both users' full_name and posts' titles.
The docs only talk about querying one attribute like this:
ThingsIndex.query(term: {full_name: 'Foo'})
There are a couple ways you could do this. Chaining is probably the easiest:
ThingsIndex.query(term: {full_name: 'Foo'}).query(term: {title: 'Foo'})
If you need to do several queries, you might consider merging them:
query = ThingsIndex.query(term: {full_name: 'Foo'})
query = query.merge(ThingsIndex.query(term: {title: 'Foo'}))
Read more about merging here: Chewy #merge docs
Make sure to set your limit or else it only shows 10 results:
query.limit(50)

How to check if value exists in params.permit?

I can't seem to figure out how to accomplish what I am trying to do here on my create method.
What I have right now works if there are no values, the item is deleted. However, if 1 or more param values exist, it passes and is saved. Not what I needed. I need an all or nothing scenario. I want to save only if all the permitted keys have their value. params.permit(:name, :description, :copyright)
Before an entry is saved using organizations.save!, I need to make sure none of the params that are permitted are nil or empty.
I search all over and can't seem to narrow down on an answer to my exact issue.
Here is my code:
class OrganizationsController < ApplicationController
def index
query_params = params.permit(:id, :name,)
if query_params.blank?
organizations = Organization.all
else
organizations = Organization.where(query_params)
end
render json: organizations, root: "organizations"
end
def create
organizations = Organization.new(organization_params)
if organization_params.present?
organizations.delete
else
organizations.save!
render json: organizations
end
end
private
def organization_params
params.permit(:name, :description, :copyright)
end
end
You should add validations to your model.
From your question i understand that you want to save details only if you get values in all the field, if not you don't want to save, right?. If yes, then adding validations to your model will give you what you wanted.
Add the following to your organization model
validates_presence_of :name
validates_presence_of :description
validates_presence_of :copyright
by doing so, the user won't be allowed to save the details unless and until all three fields have some value in it.
There is no need to use delete as the incomplete information will not be saved.
for more and advanced info click here
To check none of the values of organization_params hash is empty, you can do something like this:
organization_params.values.all? { |x| !x.empty? }
or, this:
organization_params.all? { |k,v| !v.empty? }
You can also check if any param value is empty:
organization_params.any? { |k,v| v.empty? }
So, your create method can be re-written as:
def create
organizations = Organization.new(organization_params)
if organization_params.any? { |k,v| v.empty? }
# at least one param is empty, so delete the record
organizations.delete
else
# all the params values are present, so save the record
organizations.save!
render json: organizations
end
end

List dynamic attributes in a Mongoid Model

I have gone over the documentation, and I can't find a specific way to go about this. I have already added some dynamic attributes to a model, and I would like to be able to iterate over all of them.
So, for a concrete example:
class Order
include Mongoid::Document
field :status, type: String, default: "pending"
end
And then I do the following:
Order.new(status: "processed", internal_id: "1111")
And later I want to come back and be able to get a list/array of all the dynamic attributes (in this case, "internal_id" is it).
I'm still digging, but I'd love to hear if anyone else has solved this already.
Just include something like this in your model:
module DynamicAttributeSupport
def self.included(base)
base.send :include, InstanceMethods
end
module InstanceMethods
def dynamic_attributes
attributes.keys - _protected_attributes[:default].to_a - fields.keys
end
def static_attributes
fields.keys - dynamic_attributes
end
end
end
and here is a spec to go with it:
require 'spec_helper'
describe "dynamic attributes" do
class DynamicAttributeModel
include Mongoid::Document
include DynamicAttributeSupport
field :defined_field, type: String
end
it "provides dynamic_attribute helper" do
d = DynamicAttributeModel.new(age: 45, defined_field: 'George')
d.dynamic_attributes.should == ['age']
end
it "has static attributes" do
d = DynamicAttributeModel.new(foo: 'bar')
d.static_attributes.should include('defined_field')
d.static_attributes.should_not include('foo')
end
it "allows creation with dynamic attributes" do
d = DynamicAttributeModel.create(age: 99, blood_type: 'A')
d = DynamicAttributeModel.find(d.id)
d.age.should == 99
d.blood_type.should == 'A'
d.dynamic_attributes.should == ['age', 'blood_type']
end
end
this will give you only the dynamic field names for a given record x:
dynamic_attribute_names = x.attributes.keys - x.fields.keys
if you use additional Mongoid features, you need to subtract the fields associated with those features:
e.g. for Mongoid::Versioning :
dynamic_attribute_names = (x.attributes.keys - x.fields.keys) - ['versions']
To get the key/value pairs for only the dynamic attributes:
make sure to clone the result of attributes(), otherwise you modify x !!
attr_hash = x.attributes.clone #### make sure to clone this, otherwise you modify x !!
dyn_attr_hash = attr_hash.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
or in one line:
x.attributes.clone.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
So, what I ended up doing is this. I'm not sure if it's the best way to go about it, but it seems to give me the results I'm looking for.
class Order
def dynamic_attributes
self.attributes.delete_if { |attribute|
self.fields.keys.member? attribute
}
end
end
Attributes appears to be a list of the actual attributes on the object, while fields appears to be a hash of the fields that were predefined. Couldn't exactly find that in the documentation, but I'm going with it for now unless someone else knows of a better way!
try .methods or .instance_variables
Not sure if I liked the clone approach, so I wrote one too. From this you could easily build a hash of the content too. This merely outputs it all the dynamic fields (flat structure)
(d.attributes.keys - d.fields.keys).each {|a| puts "#{a} = #{d[a]}"};
I wasn't able to get any of the above solutions to work (as I didn't want to have to add slabs and slabs of code to each model, and, for some reason, the attributes method does not exist on a model instance, for me. :/), so I decided to write my own helper to do this for me. Please note that this method includes both dynamic and predefined fields.
helpers/mongoid_attribute_helper.rb:
module MongoidAttributeHelper
def self.included(base)
base.extend(AttributeMethods)
end
module AttributeMethods
def get_all_attributes
map = %Q{
function() {
for(var key in this)
{
emit(key, null);
}
}
}
reduce = %Q{
function(key, value) {
return null;
}
}
hashedResults = self.map_reduce(map, reduce).out(inline: true) # Returns an array of Hashes (i.e. {"_id"=>"EmailAddress", "value"=>nil} )
# Build an array of just the "_id"s.
results = Array.new
hashedResults.each do |value|
results << value["_id"]
end
return results
end
end
end
models/user.rb:
class User
include Mongoid::Document
include MongoidAttributeHelper
...
end
Once I've added the aforementioned include (include MongoidAttributeHelper) to each model which I would like to use this method with, I can get a list of all fields using User.get_all_attributes.
Granted, this may not be the most efficient or elegant of methods, but it definitely works. :)

Trying to populate gmaps4rails with multiple json strings in one page

I hope I am asking this right, so please let me know if I'm way off.
The problem is trying to build a homepage that draws from multiple controllers, to display the nearest locations from multiple controllers, ie. food, businesses, ect.
Right now the individual listings pages have maps drawn from their respective
#json = Controller.all.to_gmaps4rails
How would I do something like :
#json = Controller1 Controller2 .all.to_gmaps4rails
I hope this isnt a noob question and I'm just having a bad day. Thanks guys!
edit 12.5.2011 #seanhill - this is one of the models, the other sections are very close to this format. First off, I wasn't even sure if my homepage requires it's own model, as it doesn't interact with the db at all, more pulling data from controllers that do the work. Thanks for the response Sean!
class Dining < ActiveRecord::Base
validates_uniqueness_of :name, :message => "already exists"
attr_accessible :name, :address, :cuisine, :latitude, :longitude, :about, :facebook, :twitter, :phone, :website
geocoded_by :address
after_validation :geocode, :if => :address_changed?
acts_as_gmappable :process_geocoding => false
def gmaps4rails_address
"#{self.address}"
end
def gmaps4rails_infowindow
"<h3>#{self.name}</h3><br /><h5>#{self.cuisine}</h5>"
end
def self.search(search)
if search
where('name LIKE ?', "%#{search}%")
else
scoped
end
end
end
Try this
holder = Controller1.all
holder << Controller2.all
#json = holder.flatten.map{|h| {lng: h.longitude, lat: h.latitude, class: h.class.to_s}}.to_json
Make sure to change longitude and latitude based on your column names and use js to manipulate the markers based upon class.
As the #Sean Hill said you shouldn't be calling .all on controllers but I think you have a slight misunderstanding of how things are working. Assuming you have a Model called Dining and another called Shop, when you call Dining.all or Shop.all inside class DiningsController < ApplicationController, you are calling .all on either the Dining Model or the Shop Model not on the DiningsController.
The information you display through a controller is only limited by the methods you call in it although it is best practice ensure the main focus of the information displayed is related to the respective controller.
So what you are really trying to do is get the records from multiple models and group them together to display them in a single map.
With that said the answer should read something like this
holder = Dining.all # Takes all Dining records returned as an array and sets them to holder variable
holder << Shop.all # Pushes the Shop records array into the holder with the dining records
holder.flatten!# Next we flatten the array so we only have a single array.
# Then we use the map method to run the given code one time for each instance
# in the holder array to extract the info we need. The results for every instance
# in holder are returned in an array which we then convert to_json.
#json = holder.map{|h| {lng: h.longitude, lat: h.latitude, class: h.class.to_s}}.to_json
#json1 = something.to_gmaps4rails
#json2 = something.to_gmaps4rails
#json = (JSON.parse(#json1) + JSON.parse(#json2)).to_json
I populated the map with my initial data of festivals, and then added the rides to it with javascript with this code,
<% content_for :scripts do %>
<script type="text/javascript">
Gmaps.map.callback = function() {
$.getJSON('/rides_gmap', function(data){
Gmaps.map.addMarkers(data);
});
}
</script>
<%end%>
In the rides controller I had this
def rides_gmap
#rides = Ride.all
#json = #rides.to_gmaps4rails do |ride, marker|
marker.infowindow render_to_string(:partial => "/rides/infowindow", :locals => { :ride => ride})
marker.picture({
'picture' => view_context.image_path("orange-dot.png"),
'width' => 20,
'height' => 20
})
marker.title "#{ride.address}"
marker.json({:ride_id => ride.id, :ride_festivaltype => ride.festival.festivaltype
end
respond_with #json
end
I hope this helps.

Add readable field descriptions to ActiveRecord models

I'd like to add descriptions to ActiveRecord model fields to serve as basic instructions / examples for each of the fields. Basically model metadata. I can then display these in the UI (next to the fields on a form etc.)
The way I'm planning to do it is simply create a static hashtable inside the model with the field name as the key and description as the value. I.e.
FIELD_DESCRIPTIONS = {
'category' => 'Select the category it should appear within.',
'title' => 'The title should be a short but descriptive summary.',
'description' => 'Please enter a full description.'
}
etc.
Then I would create a a basic form helper that would wrap these explanations inside of a span (initially hidden and shown via jQuery) so they could be instatiated via f.field_description(:title) or something along those lines.
Anyone have any better ideas? I'd like to keep this field metadata in the model since many views could use the same information, and I also think it's nice to have descriptions within the model when you're going back to look at the code (like how DataMapper can be used right within the model to specify fields).
To give you a little more detail on what I've already done (and it works fine) here's the code. I think there has to be a prettier way of expressing these descriptions in the model, so let me know if you have any ideas.
In model:
FIELD_DESCRIPTIONS = {
'category' => 'Select the category it should appear within.',
'title' => 'The title should be a short but descriptive summary.',
'description' => 'Please enter a full description.'
}
def self.describe_field(field)
FIELD_DESCRIPTIONS[field]
end
In application_helper.rb
def field_helper(form, field)
"<span class='field_helper'>#{form.object.class.describe_field(field)}</span>"
end
In view:
<%= field_helper(f, 'title') %>
This will produce the desired output:
<span class='field_helper'>The title should be a short but descriptive summary.</span>
UPDATE:
Ok So this is the final code I'm using based on the accepted answer.
File: /config/initializers/describe_attr.rb
if defined?(ActiveRecord)
# let's us add attribute descriptions to each AR model
class ActiveRecord::Base
def self.describe_attr(*params)
attrs = params.shift
unless attrs.nil?
case attrs
when Hash
##attr_descriptions = attrs
when Symbol
return ##attr_descriptions[attrs]
end
end
##attr_descriptions ||= {}
end
end
end
File: /app/models/project.rb
describe_attr(
:category => 'Select the category the project should appear within.',
:title => 'The title should be a short but descriptive summary of the project.',
:description => 'Describe the project in detail.',
:image => 'Upload an image for the project.'
)
File: /app/helpers/application_helper.rb
# assumes you have a style defined for attr_description
def describe_attr(form, attribute)
"<span class='attr_description'>#{form.object.class.describe_attr(attribute)}</span>"
end
File: /app/views/projects/_form.html.erb
<%= describe_attr(f, :title) %>
The hash is a reasonable simple solution, but if you're on rails 2.2 or higher you might want to try the internationalization api to do this. This would also put you in a good place if you ever wanted to add translations.
Check out the i18n guide for details, but basically you would create a config/locales/en.yml that includes your column names like:
en:
labels:
category: Select the category it should appear within.
Then in your view:
<%= t('labels.category') %>
The namespace is your call of course. Also check out section 4.1.4 for a neat way to separate translations based on your current view template.
If you want to patch ActiveRecord, then you can do something like:
# Add this at the bottom of enviroment.rb
class ActiveRecord::Base
def self.field_description(*params)
attrs = params.shift
unless attrs.nil?
case attrs
when Hash
##field_description = attrs
when Symbol
return ##field_description[attrs]
end
end
##field_description ||= {}
end
end
And inside your model you can add this line like a macro:
class Product < ActiveRecord::Base
field_description :category => 'Select the category it should appear within.',:title => 'The title should be a short but descriptive summary.',:description => 'Please enter a full description.'
end
To get the value
Product.field_description : title
You can mix your solution with the label helper itself:
f.label :title, f.describe(:title)
And in your model:
FIELD_DESCRIPTIONS = {
:category => 'Select the category it should appear within.',
:title => 'The title should be a short but descriptive summary.',
:description => 'Please enter a full description.'
}
def describe(:field)
self.class::FIELD_DESCRIPTIONS[field]
end
Be sure to check out formtastic, which includes support for internationalized (or not) field labels as per matschaffer's answer.
http://github.com/justinfrench/formtastic/

Resources