I know this might be a dumb question. I'm trying to use this xml parser
http://nokogiri.rubyforge.org/nokogiri/Nokogiri.html
I've put the code below in a controller in a bringRSS method(?), and it works fine in IRB. But how do I get values for puts link.content into my views
def bringRSS
require 'nokogiri'
require 'open-uri'
# Get a Nokogiri::HTML:Document for the page we’re interested in...
doc = Nokogiri::HTML(open('http://www.google.com/search?q=tenderlove'))
# Do funky things with it using Nokogiri::XML::Node methods...
####
# Search for nodes by css
doc.css('h3.r a.l').each do |link|
puts link.content
end
####
# Search for nodes by xpath
doc.xpath('//h3/a[#class="l"]').each do |link|
puts link.content
end
####
# Or mix and match.
doc.search('h3.r a.l', '//h3/a[#class="l"]').each do |link|
puts link.content
end
end
Your method is a rails action ? If so, the "puts" method is inappropriate. You should define some global vars that'll be accessible in the view.
#css_content = Array.new
doc.css('h3.r a.l').each do |link|
#css_content << link.content
end
You define an #css_content array which contains every of your links.
And in your view you can use that var just like you usually use them in views.
The use of puts in a Rails action will throw an exception.
Instead just assign the data to an instance variable, like this:
#link_content = []
...
doc.css('h3.r a.l').each do |link|
#link_content << link.content
end
...
You can access it later in your views with the same name
Technically, you could write directly to your response, as it behaves more or less like the object you puts to in IRB. But as mentioned above, the Rails way of doing it is to use the controller to assign to instance vars and your view to render them.
As a benefit, you'll be able to use some really nice Ruby when assigning:
#links = doc.css('h3.r a.l').map{|link|link.content}
will map what each object's content method returns, just like you did above. And since Rails extends this by giving symbol objects a to_proc method, you could shorten it to
#links = doc.css('h3.r a.l').map(&:content)
Related
I want to write a simple erb template generator to parse stored erb templates from the views using Generator module. I call the Generator from the rails controller to generate it's singleton instances and pass it the WallController by self pointer.
require 'generator'
class WallController < ApplicationController
def index
header = File.read 'app/views/application-header.html'.freeze
#instances = {header: header}
# Load view generators
Generator.generate_instances self
end
end
The first thing Generator.generate_instances actually attempts to do is to copy the WallController instance variables (hence the self pointer) to perform correct parsing of the erb templates. Then it generates methods returning erb resulted text.
require 'erb'
module Generator
def self.generate_instances environment
# Mimic class environment
if environment.superclass == ApplicationController
environment.instance_variables.each do |v|
puts "Copy instance variable '#{v}' from #{environment.name} to #{self.name}"
value = environment.instance_variable_get(v)
self.send :instance_variable_set, v, value
end
end
# Parse the ERB templates
templates = #instances
return 0 if !templates.is_a?(Hash) or templates.empty?
templates.keys.each.with_index do |key, index|
define_singleton_method key do
ERB.new(templates.values[index]).result
end
end
end
end
Usage of Generator interface will look like this:
<%=== Generator.header %>
I am new to rails but I have found out that rails controller's included files are limited to a single static structure. I didn't manage either to overwrite class Object or class Class singleton methods which could be helpful.
However, after running the above example the instance variables of WallController return the WallController class address in stead of values defined by WallController.index.
undefined method `empty?' for #<WallController:0x000000000a1f90>
Is there a correct way to distribute a rails controller instance variables among other controllers? If not, why are regular instance copy not working?
If I had to write it in ruby, that would be easy:
module Y
def self.generate_environment environment
environment.instance_variables.each do |v|
puts "Copy #{v} from #{environment.name} to #{self.name}"
value = environment.instance_variable_get v
self.instance_variable_set(v, value)
end if environment.class == Class
puts "Retrived string: #{#hello}"
end
end
class X
def self.index
#hello = 'Hello, World!'
Y.generate_environment self
end
end
X.index
This problem may be solved with viewcomponent, which allows for standard ruby code for the view controller. Also solves the problem of dividing the view code to smaller reusable components in reasonable speed.
To use the viewcomponent gem first include it to your Gemfile.
gem 'view_component', require: 'view_component/engine'
After updating your gems with bundle install, you will also need to restart your server if it's running, to apply the new gem.
Then generating the component is similar in usage to other rails generators. The first argument is a component name and the second is a component argument.
rails generate component Header site_id
Now I focus on files generated in app/component directory, view and controller code. This will simply be the controller to create the header snippet of the view.
Inside of app/component/header_component.rb can be encapsulated all the code from WallController related to the header view.
class HeaderComponent < ViewComponent::Base
def initialize(site_id:)
puts "Rendering header component for site: #{site_id}"
# Load site elements
#site = Site.find site_id
#menu_items = []
Site.all.each.with_index do |site, index|
#menu_items.push site.title => site.link
end
end
end
Similarly, put all the header view erb code to the app/component/header.html.erb.
The finished component can be generated from the view using rails render:
<%= render HeaderComponent.new(site_id: 1) %>
I've tried Facebook's Open Graph protocol in adding meta data on Rails pages. What I want to do now is to make my code not duplicated or D.R.Y.---instead of putting one meta-data header for each controller page I have, I'd like to create a base class called "MyMetaBuilder" which will be inherited by the sub-pages, but don't know where and how to start coding it...
Someone suggested that meta data property values must be dynamically generated depending on the context. For example, PlayMetaBuilder, CookMetaBuilder and so on...
Also, when unit testing the controller action, how do I verify for its existence?
Thanks a lot.
One thing is defining the tags, another is rendering them. I would do the following:
write a controller mixin (something like acts_as_metatagable) where I would define specific fields for each controller (and populate the remaining with defaults). These would be assigned to a class (or instance) variable and in this way be made accessible in the rendering step).
write an helper function which would take all my tags and turn them into html. This helper function would then be called in the layout and be rendered in the head of the document.
so, it would look a bit like this:
# homepage_controller.rb
class HomepageController < ActionController::Base
# option 1.2: include it directly here with the line below
# include ActsAsMetatagable
acts_as_metatagable :title => "Title", :url => homepage_url
end
# lib/acts_as_metatagable.rb
module ActsAsMetatagable
module MetatagableMethods
#option 2.2: insert og_tags method here and declare it as helper method
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
def self.included(base)
base.helper_method :og_tags
end
end
def acts_as_metagabable(*args)
include MetatagableMethods
# insert dirty work here
end
end
# option 1.1: include it in an initializer
# initializers/acts_as_metatagable.rb
ActiveController::Base.send :include, ActsAsMetatagable
# option 2.1: insert og_metatags helper method in an helper
module ApplicationHelper
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
end
What I did for Scoutzie, was put all metadata into a head partial, with if/else cases as such:
%meta{:type => 'Author', :content => "Kirill Zubovsky"}
%meta{'property' => "og:site_name", :content=>"Scoutzie"}
-if #designer
...
-elsif #design
...
-else
...
This way, depending on the variables that load, I know which page it is, and thereby know which metadata to include. This might not be an elegant solution, but it works and it's really simple.
I have some problem. I have many strings with keys and their belongings, keys are always the same. String looks like "key1=value1;key2=value2..." . So I made global Hash with arrays as values and want to store all data from strings into that hash so I made function:
<%
$all={}
for len in (0..$authors.length-1)
$all[$authors[len]] = Array.new #authors are defined and filled earlier
end
def add_hash(from)
the_from = from.to_s.split(";")
for one in the_from
splitted = one.split("=")
for j in (0..$authors.length.to_i-1)
if($authors[j] == splitted[0])
$all[$authors[j]] << splitted[1]
end
end
end
end
%>
but it doesn't seem to work, is something wrong in my code? (note: I can only use Ruby on Rails code)
Just for lolz)), cause of
note: I can only use Ruby on Rails code
place it in lolo.rb in initializer folder of rails app
require 'singleton'
class Lolo < Hash
include Singleton
def initialize
super([])
end
def add src
src.to_s.split(";").each do |item|
splitted = item.split("=")
self[splitted[0]] = splitted[1]
end
end
end
in any place call all =Lolo.instance to access hash, and all.add("key1=value1;key2=value2") to add elements, all.keys is authors list
and don't use global vars cause it could cause a lot of problem
Using global variable is a bad practice. Still if you want to use there is no problem.
In your code accessing hash variable using key as string is not allowed. So change the key to symbol by using to_sym
(ie) $all[$authors[len].to_sym] similarly $all[$authors[j].to_sym]
This may work.
If an action looks like:
def show
#post = Post.find(params[:id])
end
I can then do:
<%= #post.title %>
How does it pass the object to the view?
Since my action has only one line, what programming technique or pattern is used to take the #post object and pass it to the view (template) page?
I know it assumes the view will be the same name as the action, but how does it do this?
for example like this:
we will transfer MyController variables to MyView variables
class MyController
def my_action
#my_variable = 100
end
end
class MyView
def process_view_with_variables(variables)
puts variables.inspect # => [{"#my_variable"=>100}]
variables.each do |var|
var.each {|name,value| self.instance_variable_set(name,value)}
end
puts self.instance_variables # => #my_variable
# .. view_rendering here
end
end
# create new view and controller instances
my_controller = MyController.new
my_view = MyView.new
# call my_action method in MyController instance (which will initialized some variables)
my_controller.my_action
# let know about instance variables in our controller
puts my_controller.instance_variables.inspect # => ["#my_variable"]
# simple array, for store variables (this is like a proxy)
controller_variables = []
# transfer instance variables from controller to proxe
my_controller.instance_variables.each do |variable|
controller_variables << {variable => my_controller.instance_variable_get(variable)}
end
# let know which instance variables bow in proxy array
puts controller_variables.inspect # => [{"#my_variable"=>100}]
# call method process_view_with_variables which will transfer variables from proxy to view
my_view.process_view_with_variables(controller_variables) # => [{"#my_variable"=>100}]#
First you need to look at the binding class. The book "Metaprogramming Ruby" (highly recommended BTW) sums them up nicely: "A Binding is a whole scope packaged as an object. The idea is that you can create a Binding to capture the local scope and carry it around."
Then a look at the ERB class should answer your question. This example is straight from the docs:
require 'erb'
x = 42
template = ERB.new <<-EOF
The value of x is: <%= x %>
EOF
puts template.result(binding)
I hope that helps.
The Rails method Array#to_sentence allows for the following:
['a', 'b', 'c'].to_sentence # gives: "a, b, and c"
I would like to extend this method to allow it to take a block, so that you can do something like the following (where people is an array of Person objects, which have the name attribute):
people.to_sentence { |person| person.name }
# => "Bill, John, and Mark"
I don't have a problem with writing the extension method. But I can't work out where to put it. The Rails core extensions get loaded somewhere down in the depths of ActiveSupport.
My need is for a place where user-defined code is always loaded, and is pre-loaded (before any application code).
Create config/initializers/super_to_sentence.rb. All files in this directory are loaded after Rails has been loaded, so you'll have a chance to override Rails' definition of Array#to_sentence.
For code you want to load before Rails gets loaded, add it to config/environment.rb.
I like to do this:
# config/initializers/app.rb
Dir[File.join(Rails.root, "lib", "core_ext", "*.rb")].each {|l| require l }
# lib/core_ext/array.rb
class Array
def to_sentence_with_block(*args, &block)
if block_given?
# do something...
# to_sentence_without_block(*args) perhaps?
else
to_sentence_without_block(*args)
end
end
alias_method_chain :to_sentence, :block
end
I think this is an ugly idea. Why dont you just write
people.collect { |person| person.name }.to_sentence
This looks almost the same and will not confuse other people reading your code (like yourself in 2 years)
just searching around the web, seems like good practice is to add it in lib/
so if you wanna extend some ruby class (in my case, DateTime), just put the code in .rb and then in config/environment.rb:
config.after_initialize do
require "lib/super_datetime.rb"
end
my super_datetime.rb looks like this (code from http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/140184):
class DateTime
def days_in_month
self::class.civil(year, month, -1).day
end
alias dim days_in_month
def weekdays
(1..dim).to_a
end
end
restart your server and you'll have access to the new method for all DateTime objects.