2
votes

I am working with ruby 2.2 and rails 4.2.

In my application there are many CSS and JS files that I want to load from the server only when needed.But when whenever I am calling the stylesheet from independent folders in vendor using stylesheet link tag I am getting no route match error

ActionController::RoutingError (No route matches [GET] "/vendor/theme/assets/stylesheets/application.css"):

application.css being my separate manifest file and folder vendor/theme/assets/stylesheets/ containing many css files.

I have tried adding the routes to "Rails.application.config.assets.paths" but still not working.

I tried using public folder for the same purpose but still not working.

Is it possible to server these assets without pre-compiling them as only some individual pages require these assets.Kindly suggest.

Edit:

I was going through this tutorial

http://railscasts.com/episodes/279-understanding-the-asset-pipeline?autoplay=true

Assets in assets folder are working fine like http://localhost:3000/assets/application.css

But http://localhost:3000/vendor/theme/assets/stylesheets/application.css is giving route not found error

1
try after putting assets folder outside of theme folder, EX. : http://localhost:3000/vendor/assets/stylesheets/application.cssdr. strange
its working only if i put my css and js inside the default 'vendor/assets' folder.. is there some other way with which i can serve assets from some other folder in vendor?Akhil Sharma

1 Answers

1
votes
  1. From the code you have posted above, it seems like you are trying to implement css themes in Rails app. If you, here is how I have implemented the theme feature in my application.

    Whenever admin makes changes to theme file and updated it, a compiled css file gets generated/updated in the public/assets/themes/ folder with the theme name. That file is picked up by the application based on current theme applied. (I can provide code if this is what you are looking for.)

  2. To serve assets to only some specific pages, you need to implement some kind of logic that loads assets based on it. For eg. Controller Specific assets: check here.

Update for Option 1

I have Theme resource in which I save theme name and two theme colors (you can use more).

Here is how my view form looks like:

<div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
</div>
<div class="field">
   <%= f.label :color1 %><br>
   <%= f.text_field :color1 %>
</div>
<div class="field">
    <%= f.label :color2 %><br>
    <%= f.text_field :color2 %>
</div>

Here is how my ThemesController File looks like:

class ThemesController < ApplicationController
  after_action :compile_theme, only: [:create, :update]
  THEME_PATH = 'app/assets/'
  PATH = THEME_PATH + '/themes'

  def new
    @theme = Theme.new
  end

  def edit
    @theme = Theme.find(params[:id])
  end

  def create
    @theme = Theme.new(theme_params)

    if @theme.save
      # Create a scss theme file
      write_theme_file

      redirect_to @theme, notice: 'Theme was successfully created.'
    else
      render :new
    end
  end

  def update
    @theme = Theme.find(params[:id])

    if @theme.update(theme_params)
      # Create/Update a scss theme file, you can check for file exists 
      write_theme_file
      redirect_to @theme, notice: 'Theme was successfully updated.'
    else
      render :edit
    end
  end

  private
    def write_theme_file
      file = PATH + name + '.scss'
      body = "$color1: #{@theme.color1};\n$color2: #{@theme.color2};"
      File.write(file, body)
    end

    def compile_theme
      file = PATH + name + '.scss'

      theme_body = ''
      if File.exists?(file) && File.exists?(THEME_PATH + 'theme.scss')
        file = File.open(file)
        theme_body = file.read
        file.close

        file = File.open(THEME_PATH + 'theme.scss')
        theme_body = theme_body + file.read
        file.close
      else
        colors = ''
      end

      env = if Rails.application.assets.is_a?(Sprockets::Index)
        Rails.application.assets.instance_variable_get('@environment')
      else
        Rails.application.assets
      end

      Dir.mkdir(Rails.root + 'public/assets/themes') unless Dir.exists?(Rails.root + 'public/assets/themes')
      asset_file = File.join(Rails.root, 'public', asset_path(name))
      File.delete(asset_file) if File.exists?(asset_file)

      body = ::Sass::Engine.new(theme_body, {syntax: :scss, cache: false, read_cache: false, style: :compressed}).render
      File.write(File.join(Rails.root, 'public', asset_path(name)), body)
    end

    def asset_path(name)
      digest = Digest::MD5.hexdigest(name)
      "assets/themes/#{name}-#{digest}.css"
    end

    def name
      return @theme.name.downcase
    end

    def theme_params
      params.require(:theme).permit(:name, :color1, :color2)
    end
end

Explanation of controller methods:

When a new theme is created or updated, it stores the theme and creates a new .scss file inside app/assets/themes with the theme name and defined color values.

The compilation and creation of css asset file happens after create/update action is completed. The compile_theme method looks for a theme.scss (example at the bottom) file (which I have already created inside app/assets/stylesheets/ folder with basic theme color variables) and replaces $color1 and $color2 variables with color values from the current theme file. The resulting css data is being hold in theme_body variable.

body = ::Sass::Engine.new(theme_body, {syntax: :scss, cache: false, read_cache: false, style: :compressed}).render
File.write(File.join(Rails.root, 'public', asset_path(name)), body)

These two last lines will create a new file inside public/assets/themes with the resulting theme_body css content and file name with theme_name and digest.

Now you need to pick up the file in every page. To do that, in application_controller.rb file, define this

before_filter :set_theme

private
  def set_theme
    @theme = Theme.first # or change theme according to params
  end

And finally, you need to pickup the theme file in your layout file. So, add this in layouts/application.html.erb file:

<% if @theme.present? %>
   <% digest = Digest::MD5.hexdigest(@theme) %>
   <%= stylesheet_link_tag asset_url("assets/themes/#{@theme}-#{digest}.css") %>
<% end %>

Just for reference, here is how my theme.scss file looks like:

body {
  background-color: $color1;
}
#header-wrapper, footer {
  background-color: $color2;
}

That's all. Hope this helps. Let me know if you got any issues.