Hi Im trying to parse XML from a websites API with Nokogiri. Im just curious to see if Im on the right track. I have a controller wich handles the parsing and then I would like the model to initialize the necessary parameters and then display it as a simple list in the view. I was thinking something like this in the Controller:
def index
doc = Nokogiri::XML(open("http://www.mysomething.com/partner/api/1_0/somerandomkeynumber4b0/channel/11number/material/list/").read)
#news = []
doc.css("news").each do |n|
header = n.css("header").text
source_name = n.css("source_name").text
summary = n.css("summary").text
url = i.css("url").text
created_at = i.css("created_at").text
type_of_media = i.css("type_of_media").text
#news << News.new(
:header => header,)
end
and then the Model:
class News
include ActiveModel::Validations
validates_presence_of :url, :type_of_media
attr_accessor :header, :source_name, :summary, :url, :created_at, :type_of_media
def initialize(attributes = {})
#header = attributes[:header]
#source_name = attributes[:source_name]
#summary = attributes[:summary]
#url = attributes[:url]
#created_at = attributes[:created_at]
#type_of_media = attributes[:type_of_media]
end
Is this how you would do this?! Not sure Im thinking correct on this. Maybe you have any tips on a great way of incorporating Nokogiri with some other thing for the view like Google maps or something. Right now Im getting an error saying
Missing template news/index with {:formats=>[:html], :handlers=>[:builder, :rjs, :erb, :rhtml, :rxml], :locale=>[:en, :en]} in view paths
Thanks in advance!
#noodle: So this:
#news = doc.css('query').map do |n|
h = {}
%w(header source_name summary url created_at type_of_media).each do |key|
h[key.to_sym] = n.css(key).text
end
News.new(h)
end
Is equal to:
#news = []
doc.css("news").each do |n|
header = n.css("header").text
source_name = n.css("source_name").text
summary = n.css("summary").text
url = i.css("url").text
created_at = i.css("created_at").text
type_of_media = i.css("type_of_media").text
#news << News.new(
:header => header,)
end
Did I understand you correctly?! Regarding the template I have located the the problem. It was a minor misspelling. Cheers!
You're really asking two questions here..
Is my xml -> parse -> populate pipeline ok?
Yes, pretty much. As there's no conditional logic in your .each block it would be cleaner to do it like this:
#news = doc.css('query').map do |n|
#...
News.new(:blah => blah, ...)
end
.. but that's a minor point.
EDIT
You could save some typing by initializing a hash from the parsed xml and then passing that to Model.new, like:
#news = doc.css('query').map do |n|
h = {}
h[:header] = n.css('header').text
# ...
News.new(h)
end
EDIT 2
Or even shorter..
#news = doc.css('query').map do |n|
h = {}
%w(header source_name summary url created_at type_of_media).each do |key|
h[key.to_sym] = n.css(key).text
end
News.new(h)
end
In fact #inject could make that shorter still but I think that'd be a little obfuscated.
Why can't rails find my view template?
Dunno, is there one? You've not given enough details to answer that part.
Related
I want to create a page where it shows the resource created by other users but hide the resources created by current_user. is there a method or certain way in which I can do so?
class ExamplesController < ApplicationController
def index
#examples = Example.all.order("created_at DESC")
#creator_examples = Example.where(creator: current_user).order("created_at DESC") <---hide this!!!
end
You can simply manipulate your where clause into something like this:
def index
#examples = Example.all.order("created_at DESC")
#creator_examples = #examples.where.not(id: current_user.id)
end
This is for rails 4, if you're using rails 3
#creator_examples = Example.where("id != ?", current_user.id)
Note -> Example.all in rails 3 returns an array so you can't chain it with where
I am close to new at Ruby on Rails (but love it) and I cannot managge to filter acuratelly.
Mi goal is to retrieve all the votes puted on answers created by one user. And I am using act_as_votable_gem
On answer.rb I have:
class Answer < ActiveRecord::Base
acts_as_votable
belongs_to :user
belongs_to :letter
...
end
At user.rb I have:
class User < ActiveRecord::Base
acts_as_voter
has_many :answers, dependent: :destroy
...
end
At users_controller.rb comes the trick because I have the letter form on the user show action:
def show
#user = User.find(params[:id])
#letter = Letter.new(params[:letter])
#letters = #user.letters.all
#answers = #user.answers
...
#emitedupvotes = #user.votes.up.count
#emiteddownvotes = #user.votes.down.count
#totalemitedvotes = #emitedupvotes + #emiteddownvotes
#receivedupvotes = ????????
#receiveddownvotes = ???????
...
end
I have tryed:
a) (the recomended one) #receivedupvotes = #answer.votes.up.count but this one needs #answer = Answer.find(params[:id]) and I am having => "ActiveRecord::RecordNotFound at /users/1 Couldn't find Answer with id=1"
Or, if I do: Answer.find(params[:answer_id]) => "Couldn't find Answer without an ID".
b) (the most logical) #receivedupvotes = #answers.votes.up.count => "undefined method `votes' for nil:NilClass"
c) (crazy one) #receivedupvotes = #answers.user.votes.up.count => "undefined method `user'"
d) #receivedupvotes = #answers.votes.up.where("voter = #user").count => "undefined method `votes' for nil:NilClass".
So, I tryed joins (never done before):
e) #receivedupvotes = #answers.joins(:votes).votes.up.count => "undefined method `votes' for..."
f) #receivedupvotes = #answers.joins(:votes).up.count => "undefined method `up' for..."
Any help? Thanks.
I don't know if you're still stuck on this, but if so..
What gets voted on? Seems like Answer gets voted on.
If User has_many :answers, then that means #user.answers will return you an array of Answer objects. However, judging by your point b) it looks like #answers is nil, so you'll have to dig into that to find out why.
So getting #user.answers to return an actual array of Answer and not nil is your first step.
Once you get that far, #answers.votes won't work because again, #answers is an array. Each Answer has votes, so you would call votes on an instance of Answer, not on the array.
Getting back to your main goal which is to tally up all the up and down votes a user received. Since User doesn't get voted on, but Answer does get voted on, you'll want to tally up all the votes for each of the user's answers.
Here's one way:
#receivedupvotes = 0
#receiveddownvotes = 0
#user.answers.each do |answer|
#receivedupvotes = #receivedupvotes + answer.votes.up
#receiveddownvotes = #receiveddownvotes + answer.votes.down
end
Also, you should really use underscores in your variable names, like #received_up_votes. It's the recommended way for Ruby and is more readable.
Jeff. Thank you very much. It is working now as it should.
This is the answer for others to read:
#user.answers is working as it should.
An this is the working implementation for counting votes on user answers with act_as_votable:
#user_answers_received_upvotes = 0
#user_answers_received_downvotes = 0
#user.answers.each do |answer|
#user_answers_received_upvotes = #user_answers_received_upvotes + answer.votes.up.size
#user_answers_received_downvotes = #user_answers_received_downvotes + answer.votes.down.size
end
#user_answers_total_received_votes = #user_answers_received_upvotes + #user_answers_received_downvotes
I will upvote your answer when I can.
Thanks again.
This must be an easy one, but I'm stuck...
So I'm using Rails#3 with Mongoid and want to dynamically build query that would depend upon passed parameters and then execute find().
Something like
def select_posts
query = :all # pseudo-code here
if (params.has_key?(:author))
query += where(:author => params[:author]) # this is pseudo-code again
end
if (params.has_key?(:post_date))
query += where(:create_date => params[:post_date]) # stay with me
end
#post_bodies = []
Post.find(query).each do |post| # last one
#post_bodies << post.body
end
respond_to do |format|
format.html
format.json { render :json => #post_bodies }
end
end
You have a few different options to go with here - depending on how complex your actual application is going to get. Using your example directly - you could end up with something like:
query = Post.all
query = query.where(:author => params[:author]) if params.has_key?(:author)
query = query.where(:create_date => params[:post_date]) if params.has_key?(:post_date)
#post_bodies = query.map{|post| post.body}
Which works because queries (Criteria) in Mongoid are chainable.
Alternatively, if you're going to have lots more fields that you wish to leverage, you could do the following:
query = Post.all
fields = {:author => :author, :post_date => :create_date}
fields.each do |params_field, model_field|
query = query.where(model_field => params[params_field]) if params.has_key?(params_field)
end
#post_bodies = query.map{|post| post.body}
And finally, you can take it one level further and properly nest your form parameters, and name the parameters so that they match with your model, so that your params object looks something like this:
params[:post] = {:author => "John Smith", :create_date => "1/1/1970", :another_field => "Lorem ipsum"}
Then you could just do:
#post_bodies = Post.where(params[:post]).map{|post| post.body}
Of course, with that final example, you'd want to sanitize the input fields - to prevent malicious users from tampering with the behaviour.
So I want to dynamically pass filter parameters to my where method so basically I have this
#colleges = College.where(#filter).order(#sort_by).paginate(:page => params[:page], :per_page => 20)
And the #where is just a string built with this method
def get_filter_parameters
if params[:action] == 'index'
table = 'colleges'
columns = College.column_names
else
table = 'housings'
columns = Housing.column_names
end
filters = params.except(:controller, :action, :id, :sort_by, :order, :page, :college_id)
filter_keys = columns & filters.keys
#filter = ""
first = true
if filter_keys
filter_keys.each do |f|
if first
#filter << "#{table}.#{f} = '#{filters[f]}'"
first = false
else
#filter << " AND #{table}.#{f} = '#{filters[f]}'"
end
end
else
#filter = "1=1"
end
The problem is I don't know how good it is to drop raw SQL into a where method like that. I know normally you can do stuff like :state => 'PA', but how do I do that dynamically?
UPDATE
Okay so I am now passing a hash and have this:
if params[:action] == 'index'
columns = College.column_names
else
columns = Housing.column_names
end
filters = params.except(:controller, :action, :id, :sort_by, :order, :page, :college_id)
filter_keys = columns & filters.keys
#filter = {}
if filter_keys
filter_keys.each do |f|
#filter[f] = filters[f]
end
end
Will that be enough to protect against injection?
in this code here:
College.where(:state => 'PA')
We are actually passing in a hash object. Meaning this is equivalent.
filter = { :state => 'PA' }
College.where(filter)
So you can build this hash object instead of a string:
table = "colleges"
field = "state"
value = "PA"
filter = {}
filter["#{table}.#{field}"] = value
filter["whatever"] = 'omg'
College.where(filter)
However, BE CAREFUL WITH THIS!
Depending on where this info is coming from, you be opening yourself up to SQL injection attacks by putting user provided strings into the fields names of your queries. When used properly, Rails will sanitize the values in your query. However, usually the column names are fixed by the application code and dont need to be sanitized. So you may be bypassing a layer of SQL injection protection by doing it this way.
I am trying to search my postgresql db in rails. I followed the Railscasts #111 Advanced Search tutorial and it is working for the name and category of my items column in plain text. However, I want to set a min/max price on my search as well which is where I come into my problem. In my db my price is stored as a string in the format "AU $49.95". Can I convert this into a float on the fly in my scoped search? If so how? If not, what should I do?
Here is the code:
search.rb
class Search < ActiveRecord::Base
attr_accessible :keywords, :catagory, :minimum_price, :maximum_price
def items
#items ||= find_items
end
private
def find_items
scope = Item.scoped({})
scope = scope.scoped :conditions => ["to_tsvector('english', items.name) ## plainto_tsquery(?)", "%#{keywords}%"] unless keywords.blank?
scope = scope.scoped :conditions => ["items.price >= ?", "AU \$#{minimum_price.to_s}"] unless minimum_price.blank?
# scope = scope.scoped :conditions => ["items.price <= ?", "AU \$#{maximum_price.to_s}"] unless maximum_price.blank?
scope = scope.scoped :conditions => ["to_tsvector('english', items.catagory) ## ?", catagory] unless catagory.blank?
scope
end
end
searches_controller.rb
class SearchesController < ApplicationController
def new
#search = Search.new
end
def create
#search = Search.new(params[:search])
if #search.save
redirect_to #search, :notice => "Successfully created search."
else
render :action => 'new'
end
end
def show
#search = Search.find(params[:id])
end
end
Thanks for reading this far!
Use the data type numeric or money for exact numerical calculation without rounding errors - and sorting as a number (not as text).
Converting string literal to numeric should not be a performance problem at all.